關於Android休眠之后網絡連接工作情況的研究
幾個問題:
- 當Android設備休眠后,網絡連接會斷開嗎?
- 如果網絡連接不斷開收到數據包后Android設備會被喚醒嗎?
Android設備休眠后,網絡連接是否斷開
這個問題其實很好驗證,按如下步驟做一個實驗:
-
步驟
-
下載一個TCP調試軟件(做為一個TCP Server);
-
寫一個Android平台的TCP Client, 用一個Timer來向Server發送心跳;
- 連接TcpServer, 這時候Tcp調試助手會收到來自Android手機的心跳;
-
關閉手機屏幕,讓手機休眠;
-
結論
熄滅屏幕后,很快你就會發現心跳沒了,但是連接還在。證明了CPU休眠了但連接不會斷。
收到數據包是否喚醒設備
同樣做一個實驗來驗證收到數據包是否能喚醒設備
-
步驟
-
Tcp連接建立后, 讓手機進入休眠;
- 通過Tcp調試助手發送一條消息給Android客戶端;
-
打開手機屏幕, 看Client端是否收到消息;
-
結論
看到Server端發送過去的消息被打印在了屏幕上。
Android Client端代碼
基於Netty網絡框架
MainActivity.java
publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener,
TcpClient.TcpClientListener{
privateEditText edtMain;
privateButton btnConnect;
privateTcpClient tcpClient;
@Override
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtMain =(EditText) findViewById(R.id.edt_main);
btnConnect =(Button) findViewById(R.id.btn_Connect);
tcpClient =newTcpClient();
edtMain.append("onCreate<<<<<<<<<<<<<<<<<");
//Heart beat
newTimer().schedule(newTimerTask(){
@Override
publicvoid run(){
finalCharSequence time =DateFormat.format("hh:mm:ss",System.currentTimeMillis());
if(tcpClient.isConnected()){
tcpClient.send((time +"\r\n").toString().getBytes());
}
runOnUiThread(newRunnable(){
@Override
publicvoid run(){
edtMain.append(time +"\n");
}
});
}
},0,2000);
btnConnect.setOnClickListener(this);
tcpClient.setTcpClentListener(this);
}
@Override
protectedvoid onDestroy(){
edtMain.append("onDestroy>>>>>>>>>>>>>>>>");
super.onDestroy();
}
@Override
publicvoid onClick(View v){
tcpClient.connect("10.1.1.86",3008);
}
@Override
publicvoid received(finalbyte[] msg){
runOnUiThread(newRunnable(){
@Override
publicvoid run(){
edtMain.append("received ============= "+newString(msg));
}
});
}
@Override
publicvoid sent(byte[] sentData){
}
@Override
publicvoid connected(){
runOnUiThread(newRunnable(){
@Override
publicvoid run(){
btnConnect.setText("Connected");
}
});
}
@Override
publicvoid disConnected(int code,String msg){
runOnUiThread(newRunnable(){
@Override
publicvoid run(){
btnConnect.setText("Connect");
}
});
}
}
TcpClient.java
publicclassTcpClient{
//Disconnect code
publicstaticfinalint ERR_CODE_NETWORK_INVALID =0;
publicstaticfinalint ERR_CODE_TIMEOUT =1;
publicstaticfinalint ERR_CODE_UNKNOWN =2;
publicstaticfinalint ERR_CODE_DISCONNECTED =3;
privateEventLoopGroup mGroup;
privateBootstrap mBootstrap;
privateTcpClientListener mListener;
privateChannelFuture mChannelFuture;
privateboolean isConnected =false;
publicTcpClient(){
mGroup =newNioEventLoopGroup();
mBootstrap =newBootstrap();
mBootstrap.group(mGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(newChannelInitializer<SocketChannel>(){
@Override
protectedvoid initChannel(SocketChannel ch)throwsException{
ch.pipeline().addLast("decoder",newFrameDecoder());
ch.pipeline().addLast("handler",newMsgHandler());
}
});
}
publicboolean isConnected(){
return isConnected;
}
/**
* 建立連接
* @author swallow
* @createTime 2016/2/16
* @lastModify 2016/2/16
* @param host Server host
* @param port Server port
* @return
*/
publicvoid connect(finalString host,finalint port){
try{
mChannelFuture = mBootstrap.connect(host, port);
// mChannelFuture.sync();
// mChannelFuture.channel().closeFuture();
}catch(Exception e){
mGroup.shutdownGracefully();
if(mListener !=null)
mListener.disConnected(ERR_CODE_TIMEOUT, e.getMessage());
e.printStackTrace();
}
}
/**
* 斷開連接
* @author swallow
* @createTime 2016/1/29
* @lastModify 2016/1/29
* @param
* @return
*/
publicvoid disConnect(){
if(mChannelFuture ==null)
return;
if(!mChannelFuture.channel().isActive())
return;
mChannelFuture.channel().close();
mChannelFuture.channel().closeFuture();
mGroup.shutdownGracefully();
}
/**
* 消息發送
* @param
* @return
* @author swallow
* @createTime 2016/1/29
* @lastModify 2016/1/29
*/
publicvoid send(finalbyte[] data){
finalByteBuf byteBuf =Unpooled.copiedBuffer(data);
mChannelFuture.channel()
.writeAndFlush(byteBuf)
.addListener(
newChannelFutureListener(){
@Override
publicvoid operationComplete(ChannelFuture future)throwsException{
if(mListener !=null)
mListener.sent(data);
}
}
);
}
/**
* 設置TcpClient監聽
* @author swallow
* @createTime 2016/1/29
* @lastModify 2016/1/29
* @param
* @return
*/
publicvoid setTcpClentListener(TcpClientListener listener){
this.mListener = listener;
}
/**
* @className: FrameDecoder
* @classDescription: Decode to frames
* @author: swallow
* @createTime: 2015/11/23
*/
privateclassFrameDecoderextendsByteToMessageDecoder{
@Override
protectedvoid decode(ChannelHandlerContext ctx,ByteBufin,List<Object>out)throwsException{
byte[] data =newbyte[in.readableBytes()];
in.readBytes(data);
out.add(data);
// // 不夠4個字節則不予解析
// if (in.readableBytes() < 4) {
// return;
// }
// in.markReaderIndex(); // mina in.mark();
//
// // 獲取幀長度
// byte[] headers = new byte[4];
// in.readBytes(headers);
// int frameLen = Utils.bytesToInt(headers);
// if (frameLen > in.readableBytes()) {
// in.resetReaderIndex();
// return;
// }
//
// //獲取一個幀
// byte[] data = new byte[frameLen];
// in.readBytes(data);
// out.add(data);
}
}
/**
* @className: MsgHandler
* @classDescription: 經過FrameDecoder的消息處理器
* @author: swallow
* @createTime: 2015/11/23
*/
privateclassMsgHandlerextendsSimpleChannelInboundHandler<byte[]>{
@Override
protectedvoid channelRead0(ChannelHandlerContext ctx,byte[] msg)throwsException{
if(mListener !=null)
mListener.received(msg);
}
@Override
publicvoid channelActive(ChannelHandlerContext ctx)throwsException{
if(mListener !=null){
isConnected =true;
mListener.connected();
}
}
@Override
publicvoid channelInactive(ChannelHandlerContext ctx)throwsException{
if(mListener !=null){
isConnected =false;
mListener.disConnected(ERR_CODE_DISCONNECTED,"The connection is disconnected!");
}
}
@Override
publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause)throwsException{
super.exceptionCaught(ctx, cause);
}
}
/**
* @className: TcpClientListener
* @classDescription: The client listener
* @author: swallow
* @createTime: 2015/11/25
*/
publicinterfaceTcpClientListener{
void received(byte[] msg);
void sent(byte[] sentData);
void connected();
void disConnected(int code,String msg);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="cn.ecpark.devicesleep.MainActivity">
<Button
android:id="@+id/btn_Connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="Connect"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/btn_Connect">
<EditText
android:id="@+id/edt_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:editable="false"/>
</ScrollView>
</RelativeLayout>
原理
如果一開始就對Android手機的硬件架構有一定的了解,設計出的應用程序通常不會成為待機電池殺手,而要設計出正確的通信機制與通信協議也並不困難。但如果不去了解而盲目設計,可就沒准了。
首先Android手機有兩個處理器,一個叫Application Processor(AP),一個叫Baseband Processor(BP)。AP是ARM架構的處理器,用於運行Linux+Android系統;BP用於運行實時操作系統(RTOS),通訊協議棧運行於BP的RTOS之上。非通話時間,BP的能耗基本上在5mA左右,而AP只要處於非休眠狀態,能耗至少在50mA以上,執行圖形運算時會更高。另外LCD工作時功耗在100mA左右,WIFI也在100mA左右。一般手機待機時,AP、LCD、WIFI均進入休眠狀態,這時Android中應用程序的代碼也會停止執行。
Android為了確保應用程序中關鍵代碼的正確執行,提供了Wake Lock的API,使得應用程序有權限通過代碼阻止AP進入休眠狀態。但如果不領會Android設計者的意圖而濫用Wake Lock API,為了自身程序在后台的正常工作而長時間阻止AP進入休眠狀態,就會成為待機電池殺手。比如前段時間的某應用,比如現在仍然干着這事的某應用。
首先,完全沒必要擔心AP休眠會導致收不到消息推送。通訊協議棧運行於BP,一旦收到數據包,BP會將AP喚醒,喚醒的時間足夠AP執行代碼完成對收到的數據包的處理過程。其它的如Connectivity事件觸發時AP同樣會被喚醒。那么唯一的問題就是程序如何執行向服務器發送心跳包的邏輯。你顯然不能靠AP來做心跳計時。Android提供的Alarm Manager就是來解決這個問題的。Alarm應該是BP計時(或其它某個帶石英鍾的芯片,不太確定,但絕對不是AP),觸發時喚醒AP執行程序代碼。那么Wake Lock API有啥用呢?比如心跳包從請求到應答,比如斷線重連重新登陸這些關鍵邏輯的執行過程,就需要Wake Lock來保護。而一旦一個關鍵邏輯執行成功,就應該立即釋放掉Wake Lock了。兩次心跳請求間隔5到10分鍾,基本不會怎么耗電。除非網絡不穩定,頻繁斷線重連,那種情況辦法不多。
網上有說使用AlarmManager,因為AlarmManager 是Android 系統封裝的用於管理 RTC 的模塊,RTC (Real Time Clock) 是一個獨立的硬件時鍾,可以在 CPU 休眠時正常運行,在預設的時間到達時,通過中斷喚醒 CPU。
以上原文鏈接