关于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。