关于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
public class MainActivity extends AppCompatActivity implements View.OnClickListener,
TcpClient.TcpClientListener {
private EditText edtMain;
private Button btnConnect;
private TcpClient tcpClient;
@Override
protected void 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 = new TcpClient();
edtMain.append("onCreate<<<<<<<<<<<<<<<<<");
//Heart beat
new Timer().schedule(new TimerTask() {
@Override
public void run() {
final CharSequence time = DateFormat.format("hh:mm:ss", System.currentTimeMillis());
if (tcpClient.isConnected()) {
tcpClient.send((time + "\r\n").toString().getBytes());
}
runOnUiThread(new Runnable() {
@Override
public void run() {
edtMain.append(time + "\n");
}
});
}
}, 0, 2000);
btnConnect.setOnClickListener(this);
tcpClient.setTcpClentListener(this);
}
@Override
protected void onDestroy() {
edtMain.append("onDestroy>>>>>>>>>>>>>>>>");
super.onDestroy();
}
@Override
public void onClick(View v) {
tcpClient.connect("10.1.1.86", 3008);
}
@Override
public void received(final byte[] msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
edtMain.append("received ============= " + new String(msg));
}
});
}
@Override
public void sent(byte[] sentData) {
}
@Override
public void connected() {
runOnUiThread(new Runnable() {
@Override
public void run() {
btnConnect.setText("Connected");
}
});
}
@Override
public void disConnected(int code, String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
btnConnect.setText("Connect");
}
});
}
}
TcpClient.java
public class TcpClient {
//Disconnect code
public static final int ERR_CODE_NETWORK_INVALID = 0;
public static final int ERR_CODE_TIMEOUT = 1;
public static final int ERR_CODE_UNKNOWN = 2;
public static final int ERR_CODE_DISCONNECTED = 3;
private EventLoopGroup mGroup;
private Bootstrap mBootstrap;
private TcpClientListener mListener;
private ChannelFuture mChannelFuture;
private boolean isConnected = false;
public TcpClient() {
mGroup = new NioEventLoopGroup();
mBootstrap = new Bootstrap();
mBootstrap.group(mGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new FrameDecoder());
ch.pipeline().addLast("handler", new MsgHandler());
}
});
}
public boolean isConnected() {
return isConnected;
}
/**
* 建立连接
* @author swallow
* @createTime 2016/2/16
* @lastModify 2016/2/16
* @param host Server host
* @param port Server port
* @return
*/
public void connect(final String host, final int 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
*/
public void 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
*/
public void send(final byte[] data) {
final ByteBuf byteBuf = Unpooled.copiedBuffer(data);
mChannelFuture.channel()
.writeAndFlush(byteBuf)
.addListener(
new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (mListener != null)
mListener.sent(data);
}
}
);
}
/**
* 设置TcpClient监听
* @author swallow
* @createTime 2016/1/29
* @lastModify 2016/1/29
* @param
* @return
*/
public void setTcpClentListener(TcpClientListener listener) {
this.mListener = listener;
}
/**
* @className: FrameDecoder
* @classDescription: Decode to frames
* @author: swallow
* @createTime: 2015/11/23
*/
private class FrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
byte[] data = new byte[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
*/
private class MsgHandler extends SimpleChannelInboundHandler<byte[]> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
if (mListener != null)
mListener.received(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (mListener != null){
isConnected = true;
mListener.connected();
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (mListener != null){
isConnected = false;
mListener.disConnected(ERR_CODE_DISCONNECTED, "The connection is disconnected!");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
/**
* @className: TcpClientListener
* @classDescription: The client listener
* @author: swallow
* @createTime: 2015/11/25
*/
public interface TcpClientListener {
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"?>
<RelativeLayout xmlns: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。
以上原文链接