android即时消息处理机制

来源:互联网 发布:国家药品数据库下载 编辑:程序博客网 时间:2024/06/07 06:57

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           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逻辑流程图: \ 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 processor = new APNotify.Processor(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;        }       

0 0
原创粉丝点击