AlarmManager使用总结

近期准备实现一套基于socket的聊天app(主要是为了学后端),碰到的第一个问题,就是app如何高效维护一个心跳服务。

首先我排除了使用handler和thread这个组合的使用方式(不论是timetask单线程还是ScheduledExecutorService线程池,本质都是开线程),因为使用这种方式,会使一直占用cpu资源,导致手机不能处于休眠状态,导致app耗电量很大。
这里就需要AlarmManager来执行app的周期性任务了,至于为什么AlarmManager可以达到省电高效的效果?
可以参考以下链接http://blog.csdn.net/d_clock/article/details/42968039。

下面总结以下我在使用AlarmManager碰到的一些问题。

由于心跳是周期性任务,所以我想使用setRepeating方法来执行
void setRepeating (int type, long triggerAtMillis, long intervalMillis,PendingIntent operation)

这里注意一下type参数,它有4种类型:
1.ELAPSED_REALTIME //根据手机reboot之后的时间(SystemClock.elapsedRealtime() )执行alarm任务,若到了执行alarm任务的时间,但手机处于休眠状态,那么就不会执行alarm任务,直到唤醒手机
2.ELAPSED_REALTIME_WAKEUP //同上 不过会唤醒手机,立即执行alarm任务
3.RTC //同ELAPSED_REALTIME,不过是根据系统当前时间(System.currentTimeMillis() )
4.RTC_WAKEUP //同ELAPSED_REALTIME_WAKEUP,会唤醒手机,立即执行alarm任务。

然而在官方文档中会有这么一段提示:
这里写图片描述
大意就是api19之后,所有的周期任务不再会按intervalMillis精确执行,如果需要精确执行任务的话,必须把每一个任务都要设定具体的执行时间,api19之前这个方法仍然适用。
这就尴尬了,抱着试一试的心态,用了下这个方法。

Intent intent = new Intent(this, MessageSocketService.class);
intent.setAction(MessageSocketService.ACTION_HEART_BEAT);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
long startTime = SystemClock.elapsedRealtime();
        mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, startTime + 10000, 10000, pendingIntent);

设定的是每10s执行一次start MessageSocketService。

在api23手机上的执行结果如下:
AlarmManager使用总结_第1张图片

在api15手机上的执行结果如下(手机问题把间隔调到了1s):
AlarmManager使用总结_第2张图片

从执行结果上来看,在api23手机上,心跳时间间隔(单位:ms)完全跟传入的参数不符,而且执行周期也不确定,相差很大,而在api15的手机上,执行任务的时间间隔几乎等于设置的间隔时间。

最后,还是放弃了api自带的这种定期执行任务的方法。同时,api文档中提示了如果想精确按周期执行任务,就要给每一个任务设置执行时间,那么只能在每次执行一个任务之后,为下一个任务设置执行时间。那么每当该执行任务的时候,可以使用broadcast发送一个广播,通知service发送心跳请求。
AlarmManager使用总结_第3张图片

这时候可以用到setExact(int type, long triggerAtMillis, PendingIntent operation) 和set(int type, long triggerAtMillis, PendingIntent operation)方法,其中setExact是api19之后才加入的,而set方法是在api19之前就可以使用,区别是调用set方法,是由系统决定什么时候执行任务,而setExact则是让系统尽量按照设定的triggerAtMillis(发生时间)执行任务,setExact比set更加精确。
这里最关键的第三步代码:

@TargetApi(value = Build.VERSION_CODES.KITKAT)
private void startHeartBeatBroadcastTask() {
    AlarmManager alarmManager =(AlarmManager)getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(this, HeartBeatReceiver.class);
    intent.setAction(MessageSocketService.ACTION_HEART_BEAT);
    PendingIntent broadcastPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

    // nexus 5 nubia api 锤子 22 以上测试 任务间隔在5s或以上会定时任务会按时进行 5s以下则按5s进行
    //小米1s api 19机子测试  中兴 api15 小于5s也可按设置间隔执行

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){                               
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, startTime+5000, broadcastPendingIntent);
    } else { alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, startTime+5000, broadcastPendingIntent);
    }
}

测试中发现,不同api版本对间隔时间有限制,我测出来的是api19及以下的版本,间隔时间不做限制,而大于api19的手机,间隔时间最小为5s。

测试结果如下:

AlarmManager使用总结_第4张图片
实验成功,不论是锁屏状态,还是手机运行时,都能保证在5s发送一次心跳。

最后在service销毁的时候,需要取消alarmmanager的任务,代码如下

private void stopHeartBeatBroadcast() {
    AlarmManager alarmManager =(AlarmManager)getSystemService(Context.ALARM_SERVICE);
    PendingIntent broadcastPendingIntent = PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_CANCEL_CURRENT);
    alarmManager.cancel(broadcastPendingIntent);
    //注意这边的broadcastPendingIntent需要和startHeartBeatBroadcastTask方法中的broadcastPendingIntent要完全一样,否则不会取消该任务
}

最最后,这里稍微提一下,由于cpu被alarm唤醒执行任务只会有很短的时间,所以需要WakeLock锁来保证心跳的发送(如果发送失败,需要重新建立socket连接,这个过程可能会很耗时),知道服务器返回成功之后再释放锁,让机器得以休眠,现在还没开发到这一步,遇到这个问题时,会将WakeLock相关的东西补上。

你可能感兴趣的:(android)