ym_research_android_suspend_network

关于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. public class MainActivity extends AppCompatActivity implements View.OnClickListener,
  2. TcpClient.TcpClientListener {
  3. private EditText edtMain;
  4. private Button btnConnect;
  5. private TcpClient tcpClient;
  6. @Override
  7. protected void 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 = new TcpClient();
  13. edtMain.append("onCreate<<<<<<<<<<<<<<<<<");
  14. //Heart beat
  15. new Timer().schedule(new TimerTask() {
  16. @Override
  17. public void run() {
  18. final CharSequence time = DateFormat.format("hh:mm:ss", System.currentTimeMillis());
  19. if (tcpClient.isConnected()) {
  20. tcpClient.send((time + "\r\n").toString().getBytes());
  21. }
  22. runOnUiThread(new Runnable() {
  23. @Override
  24. public void run() {
  25. edtMain.append(time + "\n");
  26. }
  27. });
  28. }
  29. }, 0, 2000);
  30. btnConnect.setOnClickListener(this);
  31. tcpClient.setTcpClentListener(this);
  32. }
  33. @Override
  34. protected void onDestroy() {
  35. edtMain.append("onDestroy>>>>>>>>>>>>>>>>");
  36. super.onDestroy();
  37. }
  38. @Override
  39. public void onClick(View v) {
  40. tcpClient.connect("10.1.1.86", 3008);
  41. }
  42. @Override
  43. public void received(final byte[] msg) {
  44. runOnUiThread(new Runnable() {
  45. @Override
  46. public void run() {
  47. edtMain.append("received ============= " + new String(msg));
  48. }
  49. });
  50. }
  51. @Override
  52. public void sent(byte[] sentData) {
  53. }
  54. @Override
  55. public void connected() {
  56. runOnUiThread(new Runnable() {
  57. @Override
  58. public void run() {
  59. btnConnect.setText("Connected");
  60. }
  61. });
  62. }
  63. @Override
  64. public void disConnected(int code, String msg) {
  65. runOnUiThread(new Runnable() {
  66. @Override
  67. public void run() {
  68. btnConnect.setText("Connect");
  69. }
  70. });
  71. }
  72. }

TcpClient.java

  1. public class TcpClient {
  2. //Disconnect code
  3. public static final int ERR_CODE_NETWORK_INVALID = 0;
  4. public static final int ERR_CODE_TIMEOUT = 1;
  5. public static final int ERR_CODE_UNKNOWN = 2;
  6. public static final int ERR_CODE_DISCONNECTED = 3;
  7. private EventLoopGroup mGroup;
  8. private Bootstrap mBootstrap;
  9. private TcpClientListener mListener;
  10. private ChannelFuture mChannelFuture;
  11. private boolean isConnected = false;
  12. public TcpClient() {
  13. mGroup = new NioEventLoopGroup();
  14. mBootstrap = new Bootstrap();
  15. mBootstrap.group(mGroup)
  16. .channel(NioSocketChannel.class)
  17. .option(ChannelOption.TCP_NODELAY, true)
  18. .handler(new ChannelInitializer<SocketChannel>() {
  19. @Override
  20. protected void initChannel(SocketChannel ch) throws Exception {
  21. ch.pipeline().addLast("decoder", new FrameDecoder());
  22. ch.pipeline().addLast("handler", new MsgHandler());
  23. }
  24. });
  25. }
  26. public boolean 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. public void connect(final String host, final int 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. public void 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. public void send(final byte[] data) {
  76. final ByteBuf byteBuf = Unpooled.copiedBuffer(data);
  77. mChannelFuture.channel()
  78. .writeAndFlush(byteBuf)
  79. .addListener(
  80. new ChannelFutureListener() {
  81. @Override
  82. public void operationComplete(ChannelFuture future) throws Exception {
  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. public void 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. private class FrameDecoder extends ByteToMessageDecoder {
  107. @Override
  108. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  109. byte[] data = new byte[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. private class MsgHandler extends SimpleChannelInboundHandler<byte[]> {
  140. @Override
  141. protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
  142. if (mListener != null)
  143. mListener.received(msg);
  144. }
  145. @Override
  146. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  147. if (mListener != null){
  148. isConnected = true;
  149. mListener.connected();
  150. }
  151. }
  152. @Override
  153. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  154. if (mListener != null){
  155. isConnected = false;
  156. mListener.disConnected(ERR_CODE_DISCONNECTED, "The connection is disconnected!");
  157. }
  158. }
  159. @Override
  160. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  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. public interface TcpClientListener {
  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. <RelativeLayout xmlns: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。

以上原文链接

来自为知笔记(Wiz)

时间: 2024-11-07 23:36:30

ym_research_android_suspend_network的相关文章