zoukankan      html  css  js  c++  java
  • android即时消息处理机制

         在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逻辑流程图:
       
             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端即时消息收取,剩下的就是调整唤醒闹铃周期,平衡消息即时性与耗电的问题。
  • 相关阅读:
    sqlParameter的两种写法 以及存储过程还有sql语句(防注入)
    SqlServer2005 SQL Server 版本变更检查 警告
    禁用自带防火墙
    sql分页
    每个程序员都必须遵守的编程原则
    在PDA设备上安装SQL Server Compact
    Mcrosoft SQL Server 自定义函数
    程序员人生之路(转)
    在windows 7 上为 sqlserver 2008 启用远程访问
    在PDA设备上安装和部署 SQL Server Compac 3.5(官方版)
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/6744325.html
Copyright © 2011-2022 走看看