android即时消息处理机制

本文探讨了Android端即时消息应用中消息即时性和低功耗之间的平衡问题。通过采用Pull与Push结合的方法,利用AlarmManager和WakeLock实现智能唤醒,确保消息及时到达同时减少电量消耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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, "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端即时消息收取,剩下的就是调整唤醒闹铃周期,平衡消息即时性与耗电的问题。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值