android即时消息处理机制

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

原因是如果要保证消息即时性,通常有两种机制pull或者push。pull定时轮询机制,比较浪费服务器资源;push服务器推送机制,需要保持长连接,客户端和服务器都要求比较高(网络环境,服务器保持连接数等),它们的详细优缺点不描述了。上面这两种机制都要求客户端长期处于活动状态,前提是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, "[email protected]@===>启动空请求");
             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逻辑流程图:

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, "[email protected]@===>空请求开始处理 threadID="+Thread.currentThread().getId());
            this.isRunning=true;
            if(this.isDoShortRequest){
                if(shortEmptyRequest()&&this.isRunning)
                    longEmptyRequest(); //再开启长空请求
            }else{
                longEmptyRequest();
            }
            ImLog.d(TAG, "[email protected]@===>"+(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, "[email protected]@===>无可用网络");
                        break;
                    }
                    try {
                        if(!handleMessage())
                            break;
                     } catch (TException e) {
                         if(!this.isRunning)
                             break;
                         ImLog.d(TAG, "[email protected]@===>发请求异常:"+ e.getMessage());
                         if(exceptionHandler(e)){
                             throw new IMException("连接失败次数过多",MessageCode.IM_EXCEPTION_CONNECT);
                         }
                         continue;
                     }
                }
                ImLog.d(TAG, "[email protected]@===>"+(this.isOld?"上一个":"")+"空请求正常退出");
            } catch (Exception e) {
                ImLog.d(TAG, "[email protected]@===>"+(this.isOld?"上一个":"")+"空请求异常退出"+e.getMessage());
                if (exceptionHandler(e)) {
                    // 调用重连
                    ImLog.d(TAG, "[email protected]@===>调用重连");
                    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, "[email protected]@===>无可用网络");
                    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, "[email protected]@===>notifyID:"+NotifyID.notifyID.get());
                messageNum=  apclient.getNotifyMsgSize(sessionID, ticket, NotifyID.notifyID.get());
                NotifyError.notifyErrorNum.set(0);
                ImLog.d(TAG, "[email protected]@===>获取消息条数:"+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, "[email protected]@===>无可用网络");
                            break;
                        }
                        if(!handleMessage())
                            break;
                        receiveMessageNum++;
                        if(receiveMessageNum==messageNum) //短连接接收完后退出
                            break;
                    }
                }
                ImLog.d(TAG, "[email protected]@===>"+(this.isOld?"上一个":"")+"空请求正常退出");
            }catch(Exception e){
                ImLog.d(TAG, "[email protected]@===>"+(this.isOld?"上一个":"")+"空请求异常退出"+e.getMessage());
                if(exceptionHandler(e)){
                    isDoLongRequest=false;
                    //调用重连
                    ImLog.d(TAG, "[email protected]@===>调用重连");
                    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, "[email protected]@===>一次空请求周期开始 ");
            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, "[email protected]@===>进入接收数据处理");
                    while (processor.process(protocol,
                            protocol) == true) {
                        isStop = true;
                        break;
                    }
                    ImLog.d(TAG, "[email protected]@===>结束接收数据处理");
                } catch (TException e) {
                    ImLog.d(TAG, "[email protected]@===>接收数据处理异常");
                    isStop = true;
                }
            }
            ImLog.d(TAG, "[email protected]@===>一次空请求周期结束");
            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即时消息处理机制

时间: 2024-08-04 05:57:09

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

Android异步消息处理机制(3)asyncTask基本使用

本文翻译自android官方文档,结合自己测试,整理如下. 概述 AsyncTask抽象类,翻译过来就是异步任务,能够合理并方便的使用UI线程.该类可以实现将后台操作结果显示在UI线程中,而不需要我们自己实现子线程或者handler(当然它内部也是借助这两者实现的). 虽然AsyncTask可以提供后台运行并将结果显示在UI上,但是理想情况应该是后台操作最多只能是几秒钟,若要执行长时间的操作强烈建议使用java中的Executor,ThreadPoolExecutor,FutureTask等.

[学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起

Android异步消息处理机制(4)AsyncTask源码解析

上一章我们学习了抽象类AsyncTask的基本使用(地址:http://blog.csdn.net/wangyongge85/article/details/47988569),下面我将以问答的方法分析AsyncTask源码内容,源码版本为:API22. 1. 为什么必须在UI线程实例化我们的AsyncTask,并且必须在主线程中调用execute(Params... params)? 在分析为什么在UI线程调用之前,我们先看一下实例化AsyncTask并调用execute(Params...

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

原文地址:http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html#!comments 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了.这不,前几天为了了解android的消息处理机制,我看了Loo

android 的消息处理机制

android的消息处理机制(图+源码分析)——Looper,Handler,Message android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类.下面一一介绍: 线程的魔法师 Looper Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程.所谓Looper线程就是循环工作的线程.在程序开发中(尤

Android异步消息处理机制详解及源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 最近相对来说比较闲,加上养病,所以没事干就撸些自己之前的知识点为博客,方便自己也方便别人. 1 背景 之所以选择这个知识点来分析有以下几个原因: 逛GitHub时发现关注的isuss中有人不停的在讨论Android中的Looper , Handler , Me

Android异步消息处理机制(2)源码解析

上一章讲解了Android异步消息处理机制的基本使用,下面将简单地探寻一下异步机制背后的奥妙,源码版本为:API22. 首先,声明一下本文是在我参考了一下各位大神的文章之后才慢慢熟悉的, 若有不足之处,还望各位批评指正!.菜鸟上路,,,, 郭霖博客 鸿洋博客 刘超 深入解析android5.0系统 任玉刚博客 先后顺序按照拼音排序,无关技术本身. 先简单地总结一下Looper,MessageQueue,Message和Handler四者之间的关系: Looper和MessageQueue Loo

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为大家从源码角度介绍3者关系,然后给出一个容易记忆的结论. 1. 概述 Handler . Looper .Message 这三者都与Android异步消息处理线程相关的概念.那么什么叫异步消息处理线程呢?异步

Android异步消息处理机制——handle与Looper,AsyncTask

Android线程间的通讯采用异步消息处理机制,主要由四部分组成,包括Message,Handler,MessageQueue和Looper. 一个线程只有一个Looper与Messagequeue,但可以有多个handler实例. 例:线程A发消息Message,线程B处理消息Message. 需要在线程B中新建一个Handler实例handler,在A线程中通过该handler发送消息到线程B中的Messagequeue中, 通过B中的Looper以及先进先出的原则取出该消息并处理消息,所以