在android端做即时消息的时候。遇到的坑点是怎么保证消息即时性,又不耗电。为什么这么说呢?

     原因是假设要保证消息即时性。通常有两种机制pull或者push。

pull定时轮询机制,比較浪费server资源;pushserver推送机制,须要保持长连接,client和server都要求比較高(网络环境,server保持连接数等),它们的具体优缺点不描写叙述了。上面这两种机制都要求client长期处于活动状态。前提是cpu处于唤醒状态,而android端有休眠机制,保证手机在大部分时间里都是处于休眠,减少耗电量,延长机时间。

手机休眠后,线程处理暂停状态,这样前面说的两种方式,都会处于暂停状态,从而导致休眠后就无法收消息问题。可能有人说手机有唤醒机制。假设一直唤醒呢,这样导致做的软件是耗电大户,基本不要一天手机电量就被干光,想想睡觉前有半格电,早上起来电量干光被关机,郁闷的心情顿时油然而生,所以这样干是不行的,会直接导致软件被卸载。

      即时与耗电比較矛盾,怎么办呢?解决的方法就是平衡了,保证即时性的同一时候又尽量减少耗电。

一、唤醒机制

             手机有休眠机制,它也提供了唤醒机制。这样我们就能够在休眠的时候,唤醒我们的程序继续干活。关于唤醒说两个类:AlarmManager和WakeLock:
             AlarmManager手机的闹铃机制,走的时钟机制不一样,确保休眠也可以计时准确。而且唤醒程序。详细使用方法就不说了,AlarmManager可以唤醒cpu。将程序唤醒。可是它的唤醒时间,只确保它唤醒的意图对象接收方法运行完成。至于方法里面调用其它的异步处理,它不保证,所以一般他唤醒的时间比較短。做完即继续休眠。

假设要确保异步之外的事情做完,就得申请WakeLock,确保手机不休眠,不然事情干得一半,手机就休眠了。

            这里使用AlarmManager和WakeLock结合的方式,把收消息放在异步去做,详细怎么做后面再看。先说说闹铃唤醒周期问题,为确保消息即时,当然是越短越好,可是为了确保省电,就不能太频繁了。
           策略一、能够採用水波策略。重设闹铃:開始密集调度,逐渐增长。如:30秒開始,每次递增5秒,一直递增到25分钟,就固定周期。
           策略二、能够採用闲时忙时策略,白天忙。周期密集,晚上闲时。周期长。

           策略三、闹铃调整策略,确保收消息即时。到收到消息时,就又一次初始化那闹铃时间。由最短周期開始。确保聊天状态下。即时。
           策略四、WakeLock唤醒。检測手机屏幕是是否亮起,推断是否须要获取唤醒锁。减少唤醒次数。

           
           1、设置闹铃
           
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, (triggerAtTime + time), pi);

2、闹铃时间优化

public class AlarmTime {
public static final AtomicLong alarmTime=new AtomicLong(0);
/**
* 初始化闹铃时间,重连或者收到消息初始化一下
*/
public static long initAlarmTime(){
alarmTime.set(Global.ALARM_TRIGGER_TIME);
return alarmTime.get();
}
/**
* 优化闹铃时间。重连错误数超过一定次数。优化闹铃时间再尝试重连到错误数
* 10分钟,30秒、30秒、;;。。到达错误数,10分钟 。;;;。
* @return
*/
public static long optimizeAlarmTime(){
alarmTime.set(Global.ALARM_TRIGGER_OPTIMIZE_TIME);//10分钟
return alarmTime.get();
}
public static long incrementTime(){
long time =alarmTime.get();
if(time==0)
return alarmTime.addAndGet(Global.ALARM_TRIGGER_TIME);//默认30秒開始
else if(time<Global.ALARM_TRIGGER_MAX_TIME)//25分钟
return alarmTime.addAndGet(Global.ALARM_TRIGGER_TIME_INCREMENT);//每次递增5秒
else
return time;
}
}

3、唤醒机制

public final class IMWakeLock {
private static final String TAG = IMWakeLock.class.getSimpleName();
private WakeLock wakeLock = null;
private String tag="";
private PowerManager pm;
public IMWakeLock(Context paramContext,String tag){
this.tag =tag;
pm= ((PowerManager) paramContext.
getSystemService(Context.POWER_SERVICE));
wakeLock = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK , tag);
}
/**
* 获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持执行
*/
public synchronized void acquireWakeLock() {
if(!pm.isScreenOn()) {
if (null != wakeLock&&!wakeLock.isHeld()) {
ImLog.d(TAG, tag+"@@===>获取唤醒休眠锁");
wakeLock.acquire();
}
}
}
/**
* 释放设备电源锁
*/
public synchronized void releaseWakeLock() {
if (null != wakeLock && wakeLock.isHeld()) {
ImLog.d(TAG, tag+"@@===>释放唤醒休眠锁");
wakeLock.release();
}
}
public synchronized void finalize(){
if (null != wakeLock && wakeLock.isHeld()) {
ImLog.d(TAG, tag+"@@===>释放唤醒休眠锁");
wakeLock.release();
}
wakeLock = null;
}
public boolean isScreenOn(){
return pm.isScreenOn();
}
}

4、唤醒时机

 private void startApNotify(){
if(this.sessionID==0||this.ticket==null)
return;
if(wakeLock.isScreenOn()){
ImLog.d(TAG, "NotifyService@@===>启动空请求");
apNotifyThread=new ApNotifyThread(this,false);
}else{
wakeLock.acquireWakeLock();
apNotifyThread=new ApNotifyThread(this,true); }
exec=Executors.newSingleThreadExecutor();
exec.execute(apNotifyThread);
exec.shutdown();
}

           唤醒机制想好了,可是假设唤醒后,长时间不释放唤醒锁也不行。所以这里就得考虑收消息机制。


二、消息收取

           消息收取,採用push与pull结合方式。为什么採用两种结合方式呢?先看看特性
           push:即时,维持连接,耗时长。
           pull:被动。维持连接,处理时间短。
           依据手机的唤醒和休眠机制。能够分析出push适合手机在位休眠的时候,未休眠,保持长连接,确保消息即时收取。而pull适合手机休眠状态(休眠状态没有办法推断,仅仅能依据屏幕亮起否推断。曲线救国了)。也就是休眠后。用唤醒机制唤醒,pull下有没有消息,没有消息释放休眠锁,有消息收取消息。收取完后释放休眠锁,确保唤醒时间最短。减少耗电量。

           push逻辑流程图:
   

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FnY3k=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

         pull逻辑流程图:
         

            代码处理部分:
            
public  class ApNotifyThread extends Thread{
private static final String TAG = ApNotifyThread.class.getSimpleName();
protected volatile boolean isRunning=false;
protected volatile APHold.Client client;
protected volatile VRVTHttpClient thc;
protected volatile TProtocol protocol;
protected volatile long sessionID;
protected volatile String ticket;
protected final long ERRORNUM=15;
protected NotifyService service;
protected boolean isOld=false;
protected boolean isDoShortRequest=false;
public ApNotifyThread(NotifyService service,boolean isDoShortRequest){
this.sessionID=service.getSessionID();
this.ticket=service.getTicket();
this.service=service;
this.isDoShortRequest=isDoShortRequest;
}
@Override
public void run(){
ImLog.d(TAG, "ApNotifyThread@@===>空请求開始处理 threadID="+Thread.currentThread().getId());
this.isRunning=true;
if(this.isDoShortRequest){
if(shortEmptyRequest()&&this.isRunning)
longEmptyRequest(); //再开启长空请求
}else{
longEmptyRequest();
}
ImLog.d(TAG, "ApNotifyThread@@===>"+(this.isOld?"上一个":"")+"空请求终止 threadID="+Thread.currentThread().getId());
this.isRunning=false;
}
/**
* 初始化
* @param isLongTimeOut
* @throws Exception
*/
private void init(boolean isLongTimeOut) throws Exception{
thc= NotifyHttpClientUtil.getVRVTHttpClient(isLongTimeOut);
protocol = new TBinaryProtocol(thc);
}
/**
* 长空请求
*/
private void longEmptyRequest(){
try{
this.init(true);
client= new APHold.Client(protocol);
for (;;) {
if(!NetStatusUtil.havActiveNet(IMApp.getApp())){
ImLog.d(TAG, "longEmptyRequest@@===>无可用网络");
break;
}
try {
if(!handleMessage())
break;
} catch (TException e) {
if(!this.isRunning)
break;
ImLog.d(TAG, "longEmptyRequest@@===>发请求异常:"+ e.getMessage());
if(exceptionHandler(e)){
throw new IMException("连接失败次数过多",MessageCode.IM_EXCEPTION_CONNECT);
}
continue;
}
}
ImLog.d(TAG, "longEmptyRequest@@===>"+(this.isOld?"上一个":"")+"空请求正常退出");
} catch (Exception e) {
ImLog.d(TAG, "longEmptyRequest@@===>"+(this.isOld?"上一个":"")+"空请求异常退出"+e.getMessage());
if (exceptionHandler(e)) {
// 调用重连
ImLog.d(TAG, "longEmptyRequest@@===>调用重连");
this.service.getDataSyncer().setValue(UserProfile.RECONNECT, "0");
}
}finally{
close();
}
}
/**
* 短空请求
* @return
*/
private boolean shortEmptyRequest(){
boolean isDoLongRequest=true;
try{
long messageNum=0;
if(!NetStatusUtil.havActiveNet(IMApp.getApp())){
ImLog.d(TAG, "shortEmptyRequest@@===>无可用网络");
return false;
}
this.init(false);
//获取消息数
APService.Client apclient = new APService.Client(protocol);
this.service.getDataSyncer().setValue(UserProfile.LASTREQUESTTIME, String.valueOf(SystemClock.elapsedRealtime()));
ImLog.d(TAG, "shortEmptyRequest@@===>notifyID:"+NotifyID.notifyID.get());
messageNum= apclient.getNotifyMsgSize(sessionID, ticket, NotifyID.notifyID.get());
NotifyError.notifyErrorNum.set(0);
ImLog.d(TAG, "shortEmptyRequest@@===>获取消息条数:"+messageNum);
if(messageNum==-1)
throw new IMException("session 失效",MessageCode.IM_BIZTIPS_SESSIONINVAILD);
//假设有消息接收消息
if(messageNum>0&&this.isRunning){
long receiveMessageNum=0;
client= new APHold.Client(protocol);
for (;;) {
if(!NetStatusUtil.havActiveNet(IMApp.getApp())){
ImLog.d(TAG, "shortEmptyRequest@@===>无可用网络");
break;
}
if(!handleMessage())
break;
receiveMessageNum++;
if(receiveMessageNum==messageNum) //短连接接收完后退出
break;
}
}
ImLog.d(TAG, "shortEmptyRequest@@===>"+(this.isOld? "上一个":"")+"空请求正常退出");
}catch(Exception e){
ImLog.d(TAG, "shortEmptyRequest@@===>"+(this.isOld?"上一个":"")+"空请求异常退出"+e.getMessage());
if(exceptionHandler(e)){
isDoLongRequest=false;
//调用重连
ImLog.d(TAG, "shortEmptyRequest@@===>调用重连");
this.service.getDataSyncer().setValue(UserProfile.RECONNECT, "0");
}
}
finally{
close();
this.service.releaseWakeLock();
}
return isDoLongRequest;
}
/**
* 异常处理 推断是否重连
* @param e
* @return
*/
private boolean exceptionHandler(Exception e){
boolean isReconnect=false;
if ( e instanceof IMException) {
isReconnect=true;
}else if (!(e instanceof SocketTimeoutException)&&!(e instanceof NoHttpResponseException)) {
NotifyError.notifyErrorNum.incrementAndGet();
if(NotifyError.notifyErrorNum.get()>this.ERRORNUM){
isReconnect=true;
NotifyError.notifyErrorNum.set(0);
}
}else
NotifyError.notifyErrorNum.set(0);
e.printStackTrace();
return isReconnect;
}
/**
* 空请求发送和接收数据处理
* @throws TException
*/
private boolean handleMessage() throws TException{
if(!this.isRunning)
return false;
ImLog.d(TAG, "handleMessage@@===>sessionID "+sessionID);
SendEmptyRequestReq req = new SendEmptyRequestReq();
req.setSessionID(sessionID);
req.setTicket(ticket);
req.setNotifyID(NotifyID.notifyID.get());
ImLog.d(TAG, "handleMessage@@===>一次空请求周期開始 ");
this.service.getDataSyncer().setValue(UserProfile.LASTREQUESTTIME, String.valueOf(SystemClock.elapsedRealtime()));
client.SendEmptyRequest(req);
NotifyError.notifyErrorNum.set(0);
if(!this.isRunning)
return false;
APNotifyImpl iface = new APNotifyImpl();
APNotify.Processor<Iface> processor = new APNotify.Processor<Iface>(iface);
boolean isStop = false;
while (!isStop) {
try {
ImLog.d(TAG, "handleMessage@@===>进入接收数据处理");
while (processor.process(protocol,
protocol) == true) {
isStop = true;
break;
}
ImLog.d(TAG, "handleMessage@@===>结束接收数据处理");
} catch (TException e) {
ImLog.d(TAG, "handleMessage@@===>接收数据处理异常");
isStop = true;
}
}
ImLog.d(TAG, "handleMessage@@===>一次空请求周期结束");
if(!iface.isSessionVaild){//后台报session 失效
this.service.setSessionID(0);
this.service.setTicket(null);
return false;
}
//重设闹铃
this.service.getDataSyncer().setValue(UserProfile.ALARM_TTIME, "0");
return true;
}
/**
* 关闭连接
*/
private void close() {
synchronized(this){
if (thc != null) {
thc.shutdown();
thc.close();
thc=null;
}
}
if (client != null && client.getInputProtocol() != null) {
client.getInputProtocol().getTransport().close();
client.getOutputProtocol().getTransport().close();
}
}
/**
* 线程中断
*/
public void interrupt() {
this.isRunning=false;
this.isOld=true;
close();
super.interrupt();
}
/**
* 推断是否在执行状态
*/
public boolean isRunning(){
return isRunning;
}


      依据上面的分析优化,android端即时消息收取,剩下的就是调整唤醒闹铃周期,平衡消息即时性与耗电的问题。

android即时消息处理机制的更多相关文章

  1. 转 Android的消息处理机制(图+源码分析)——Looper,Handler,Message

    作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种 ...

  2. 【转】android的消息处理机制(图+源码分析)——Looper,Handler,Message

    原文地址:http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html#!comments 作为一个大三的预备程序员,我学习 ...

  3. android的消息处理机制(图+源码分析)——Looper,Handler,Message

    android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了.这不,前几天为了了解android ...

  4. 《Android进阶》之第三篇 深入理解android的消息处理机制

    Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系 android的消息处理机制(图+源码分析)——Looper,Handler,Message an ...

  5. Android异步消息处理机制(多线程)

    当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去执行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用. ...

  6. 【转载】Android异步消息处理机制详解及源码分析

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  7. android的消息处理机制——Looper,Handler,Message

    在开始讨论android的消息处理机制前,先来谈谈一些基本相关的术语. 通信的同步(Synchronous):指向客户端发送请求后,必须要在服务端有回应后客户端才继续发送其它的请求,所以这时所有请求将 ...

  8. Android异步消息处理机制

    安卓子线程无法直接更改UI,所以需要异步消息处理机制来解决 <?xml version="1.0" encoding="utf-8"?><Li ...

  9. android基础(六)android的消息处理机制

    Android中的消息处理机制由四个部分组成:Message.Handler.MessageQueue和Looper,并且MessageQueue封装在Looper中,我们一般不直接与MQ打交道. 一 ...

随机推荐

  1. Facebook 开源动画库 pop

    官网:https://github.com/facebook/pop Demo: https://github.com/callmeed/pop-playground 一:pop的基本构成: POPP ...

  2. UITabBarController 的配置

    UITabBarController --> UITabBar Customizing Appearance backgroundImage  背景图片 selectedImageTintCol ...

  3. 子查询一(WHERE中的子查询)

    子查询 子查询就是指的在一个完整的查询语句之中,嵌套若干个不同功能的小查询,从而一起完成复杂查询的一种编写形式,为了让读者更加清楚子查询的概念. 子查询返回结果子查询可以返回的数据类型一共分为四种: ...

  4. IE hack 汇总

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. [Python]网络爬虫(七):Python中的正则表达式教程

    转自:http://blog.csdn.net/pleasecallmewhy/article/details/8929576#t4 接下来准备用糗百做一个爬虫的小例子. 但是在这之前,先详细的整理一 ...

  6. Java类(继承)初始化顺序

    /** * Created by xfyou on 2016/11/2. * Java继承的初始化 */ public class Beetle extends Insect { int k = pr ...

  7. SettingsMyEclipse

      迁移时间--2017年5月20日10:39:42 Author:Marydon-----------------------------------MyEclipse单独设置项---------- ...

  8. 简单四步開始树莓派上的Docker之旅

    大概这篇博文发表之后,应该算是我个人的第一篇翻译作品了,翻译的可能不是非常到位,望各位看官大刀砍过来. 原文链接:http://resin.io/blog/docker-on-raspberry-pi ...

  9. 笨鸟先飞之Java(一)--使用struts2框架实现文件上传

    无论是.net还是Java,我们最常接触到的就是文件的上传和下载功能,在Java里要实现这两个经常使用功能会有非常多种解决方案,可是struts2的框架却能给我们一个比較简单的方式,以下就一起来看吧: ...

  10. 【laravel5.4】引入自定义类库+卸载已有的自定义库(以引入钉钉应用为例)composer dumpautoload -o

    本文之前,首先感谢: Azeroth_Yang  传送门:https://blog.csdn.net/zwrj1130/article/details/73467320 强烈建议引入的类 都是含有命名 ...