關於Android休眠之后網絡連接工作情況的研究


關於Android休眠之后網絡連接工作情況的研究

幾個問題:

  1. 當Android設備休眠后,網絡連接會斷開嗎?
  2. 如果網絡連接不斷開收到數據包后Android設備會被喚醒嗎?

Android設備休眠后,網絡連接是否斷開

這個問題其實很好驗證,按如下步驟做一個實驗:

  • 步驟

  1. 下載一個TCP調試軟件(做為一個TCP Server);

  2. 寫一個Android平台的TCP Client, 用一個Timer來向Server發送心跳;

  3. 連接TcpServer, 這時候Tcp調試助手會收到來自Android手機的心跳;
  4. 關閉手機屏幕,讓手機休眠;

  • 結論

熄滅屏幕后,很快你就會發現心跳沒了,但是連接還在。證明了CPU休眠了但連接不會斷。

收到數據包是否喚醒設備

同樣做一個實驗來驗證收到數據包是否能喚醒設備

  • 步驟

  1. Tcp連接建立后, 讓手機進入休眠;

  2. 通過Tcp調試助手發送一條消息給Android客戶端;
  3. 打開手機屏幕, 看Client端是否收到消息;

  • 結論

看到Server端發送過去的消息被打印在了屏幕上。

Android Client端代碼

基於Netty網絡框架

MainActivity.java

  1. publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener,
  2. TcpClient.TcpClientListener{
  3. privateEditText edtMain;
  4. privateButton btnConnect;
  5. privateTcpClient tcpClient;
  6. @Override
  7. protectedvoid onCreate(Bundle savedInstanceState){
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. edtMain =(EditText) findViewById(R.id.edt_main);
  11. btnConnect =(Button) findViewById(R.id.btn_Connect);
  12. tcpClient =newTcpClient();
  13. edtMain.append("onCreate<<<<<<<<<<<<<<<<<");
  14. //Heart beat
  15. newTimer().schedule(newTimerTask(){
  16. @Override
  17. publicvoid run(){
  18. finalCharSequence time =DateFormat.format("hh:mm:ss",System.currentTimeMillis());
  19. if(tcpClient.isConnected()){
  20. tcpClient.send((time +"\r\n").toString().getBytes());
  21. }
  22. runOnUiThread(newRunnable(){
  23. @Override
  24. publicvoid run(){
  25. edtMain.append(time +"\n");
  26. }
  27. });
  28. }
  29. },0,2000);
  30. btnConnect.setOnClickListener(this);
  31. tcpClient.setTcpClentListener(this);
  32. }
  33. @Override
  34. protectedvoid onDestroy(){
  35. edtMain.append("onDestroy>>>>>>>>>>>>>>>>");
  36. super.onDestroy();
  37. }
  38. @Override
  39. publicvoid onClick(View v){
  40. tcpClient.connect("10.1.1.86",3008);
  41. }
  42. @Override
  43. publicvoid received(finalbyte[] msg){
  44. runOnUiThread(newRunnable(){
  45. @Override
  46. publicvoid run(){
  47. edtMain.append("received ============= "+newString(msg));
  48. }
  49. });
  50. }
  51. @Override
  52. publicvoid sent(byte[] sentData){
  53. }
  54. @Override
  55. publicvoid connected(){
  56. runOnUiThread(newRunnable(){
  57. @Override
  58. publicvoid run(){
  59. btnConnect.setText("Connected");
  60. }
  61. });
  62. }
  63. @Override
  64. publicvoid disConnected(int code,String msg){
  65. runOnUiThread(newRunnable(){
  66. @Override
  67. publicvoid run(){
  68. btnConnect.setText("Connect");
  69. }
  70. });
  71. }
  72. }

TcpClient.java

  1. publicclassTcpClient{
  2. //Disconnect code
  3. publicstaticfinalint ERR_CODE_NETWORK_INVALID =0;
  4. publicstaticfinalint ERR_CODE_TIMEOUT =1;
  5. publicstaticfinalint ERR_CODE_UNKNOWN =2;
  6. publicstaticfinalint ERR_CODE_DISCONNECTED =3;
  7. privateEventLoopGroup mGroup;
  8. privateBootstrap mBootstrap;
  9. privateTcpClientListener mListener;
  10. privateChannelFuture mChannelFuture;
  11. privateboolean isConnected =false;
  12. publicTcpClient(){
  13. mGroup =newNioEventLoopGroup();
  14. mBootstrap =newBootstrap();
  15. mBootstrap.group(mGroup)
  16. .channel(NioSocketChannel.class)
  17. .option(ChannelOption.TCP_NODELAY,true)
  18. .handler(newChannelInitializer<SocketChannel>(){
  19. @Override
  20. protectedvoid initChannel(SocketChannel ch)throwsException{
  21. ch.pipeline().addLast("decoder",newFrameDecoder());
  22. ch.pipeline().addLast("handler",newMsgHandler());
  23. }
  24. });
  25. }
  26. publicboolean isConnected(){
  27. return isConnected;
  28. }
  29. /**
  30. * 建立連接
  31. * @author swallow
  32. * @createTime 2016/2/16
  33. * @lastModify 2016/2/16
  34. * @param host Server host
  35. * @param port Server port
  36. * @return
  37. */
  38. publicvoid connect(finalString host,finalint port){
  39. try{
  40. mChannelFuture = mBootstrap.connect(host, port);
  41. // mChannelFuture.sync();
  42. // mChannelFuture.channel().closeFuture();
  43. }catch(Exception e){
  44. mGroup.shutdownGracefully();
  45. if(mListener !=null)
  46. mListener.disConnected(ERR_CODE_TIMEOUT, e.getMessage());
  47. e.printStackTrace();
  48. }
  49. }
  50. /**
  51. * 斷開連接
  52. * @author swallow
  53. * @createTime 2016/1/29
  54. * @lastModify 2016/1/29
  55. * @param
  56. * @return
  57. */
  58. publicvoid disConnect(){
  59. if(mChannelFuture ==null)
  60. return;
  61. if(!mChannelFuture.channel().isActive())
  62. return;
  63. mChannelFuture.channel().close();
  64. mChannelFuture.channel().closeFuture();
  65. mGroup.shutdownGracefully();
  66. }
  67. /**
  68. * 消息發送
  69. * @param
  70. * @return
  71. * @author swallow
  72. * @createTime 2016/1/29
  73. * @lastModify 2016/1/29
  74. */
  75. publicvoid send(finalbyte[] data){
  76. finalByteBuf byteBuf =Unpooled.copiedBuffer(data);
  77. mChannelFuture.channel()
  78. .writeAndFlush(byteBuf)
  79. .addListener(
  80. newChannelFutureListener(){
  81. @Override
  82. publicvoid operationComplete(ChannelFuture future)throwsException{
  83. if(mListener !=null)
  84. mListener.sent(data);
  85. }
  86. }
  87. );
  88. }
  89. /**
  90. * 設置TcpClient監聽
  91. * @author swallow
  92. * @createTime 2016/1/29
  93. * @lastModify 2016/1/29
  94. * @param
  95. * @return
  96. */
  97. publicvoid setTcpClentListener(TcpClientListener listener){
  98. this.mListener = listener;
  99. }
  100. /**
  101. * @className: FrameDecoder
  102. * @classDescription: Decode to frames
  103. * @author: swallow
  104. * @createTime: 2015/11/23
  105. */
  106. privateclassFrameDecoderextendsByteToMessageDecoder{
  107. @Override
  108. protectedvoid decode(ChannelHandlerContext ctx,ByteBufin,List<Object>out)throwsException{
  109. byte[] data =newbyte[in.readableBytes()];
  110. in.readBytes(data);
  111. out.add(data);
  112. // // 不夠4個字節則不予解析
  113. // if (in.readableBytes() < 4) {
  114. // return;
  115. // }
  116. // in.markReaderIndex(); // mina in.mark();
  117. //
  118. // // 獲取幀長度
  119. // byte[] headers = new byte[4];
  120. // in.readBytes(headers);
  121. // int frameLen = Utils.bytesToInt(headers);
  122. // if (frameLen > in.readableBytes()) {
  123. // in.resetReaderIndex();
  124. // return;
  125. // }
  126. //
  127. // //獲取一個幀
  128. // byte[] data = new byte[frameLen];
  129. // in.readBytes(data);
  130. // out.add(data);
  131. }
  132. }
  133. /**
  134. * @className: MsgHandler
  135. * @classDescription: 經過FrameDecoder的消息處理器
  136. * @author: swallow
  137. * @createTime: 2015/11/23
  138. */
  139. privateclassMsgHandlerextendsSimpleChannelInboundHandler<byte[]>{
  140. @Override
  141. protectedvoid channelRead0(ChannelHandlerContext ctx,byte[] msg)throwsException{
  142. if(mListener !=null)
  143. mListener.received(msg);
  144. }
  145. @Override
  146. publicvoid channelActive(ChannelHandlerContext ctx)throwsException{
  147. if(mListener !=null){
  148. isConnected =true;
  149. mListener.connected();
  150. }
  151. }
  152. @Override
  153. publicvoid channelInactive(ChannelHandlerContext ctx)throwsException{
  154. if(mListener !=null){
  155. isConnected =false;
  156. mListener.disConnected(ERR_CODE_DISCONNECTED,"The connection is disconnected!");
  157. }
  158. }
  159. @Override
  160. publicvoid exceptionCaught(ChannelHandlerContext ctx,Throwable cause)throwsException{
  161. super.exceptionCaught(ctx, cause);
  162. }
  163. }
  164. /**
  165. * @className: TcpClientListener
  166. * @classDescription: The client listener
  167. * @author: swallow
  168. * @createTime: 2015/11/25
  169. */
  170. publicinterfaceTcpClientListener{
  171. void received(byte[] msg);
  172. void sent(byte[] sentData);
  173. void connected();
  174. void disConnected(int code,String msg);
  175. }
  176. }

activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:paddingBottom="@dimen/activity_vertical_margin"
  7. android:paddingLeft="@dimen/activity_horizontal_margin"
  8. android:paddingRight="@dimen/activity_horizontal_margin"
  9. android:paddingTop="@dimen/activity_vertical_margin"
  10. tools:context="cn.ecpark.devicesleep.MainActivity">
  11. <Button
  12. android:id="@+id/btn_Connect"
  13. android:layout_width="match_parent"
  14. android:layout_height="wrap_content"
  15. android:layout_alignParentBottom="true"
  16. android:text="Connect"/>
  17. <ScrollView
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent"
  20. android:layout_above="@+id/btn_Connect">
  21. <EditText
  22. android:id="@+id/edt_main"
  23. android:layout_width="match_parent"
  24. android:layout_height="match_parent"
  25. android:editable="false"/>
  26. </ScrollView>
  27. </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。

以上原文鏈接






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM