android Service重启问题,结合AlarmManager实现定时任务


        当启动service进行后台任务的时候,我们一般的 做法是启动一个线程,然后通过sleep方法来控制进行定时的任务,如轮询操作,消息推送。这种service的资源是很容易被回收的,虽然service的优先级很高,但是还没有前台的activity的优先极高,所以一旦资源被回收,service会停止运行。
        service被回收是我们不能控制的,但是我们可以控制service的重启活动。在service的onStartCommand
方法中可以返回一个参数来控制重启活动
        1、Service.START_STICKY,就会在资源被回收(用手机加速程序加速)或者程序异常(异常结束程序,测试的时候手动抛出一个异常)的时候重新调用oncreate、onStartCommand来启动服务。但是不能连续重启两次。像音乐播放器,这种需要持续运行的,断开后可以马上重启,但是传送过来的intent为null。
            这种模式通常用于处理自身状态的service,以及需要通过startService和stopService显示的启动和终止的Service,如音乐播放器的service
        2、START_NOT_STICKY就不会重新启动服务。比如说更新操作,他会考虑到资源竞争,比较谨慎,不会自动启动service。此处onstartcommand方法中的intent是本次传送过来的intent。当被异常终止后,只有重新startService 调用,这个servie才会启动。
            这种模式比较谨慎,当停止后,它会在下一个调度中尝试重新启动,不会再资源竞争的时候重启,对于处理特殊请求,尤其诸如更新操作或者网络轮询这样的定期处理。
        3、START_REDELIVER_INTENT不会马上重启service。需要在下次调用启动这个service的时候会获得上次传入的intent,然后执行两次onStartCommand方法。只执行一次oncreat方法。也就是说可以把上次没有完成的工作这一次也完成(传送过来的intent是保留的上一次的intent和本次传送的intent)。一般用于不是无限循环的任务。
        这种模式是要确保service中的命令得以完成。--例如在时效性比较重要的时候
注意,在处理完成后,每种模式都要求使用stopService和stopSelf显式的停止service。

从以上的参数中可以看出,service的重启操作不能只依靠参数返回值来实现,能够重启的只有 Service.START_STICKY,但是这种方法只能重启一次,再次断开的时候就不能实现重连操作了。所以必须使用AlarmManager的定时任务来实现网络的轮询等定时任务。可以看到使用START_NOT_STICKY的返回值是最适合网络轮询操作的。


AlarmManager的工作原理。alarmmanager会定时的发出一条广播,然后在自己的项目里面注册这个广播,重写onReceive方法,在这个方法里面启动一个service,然后在service里面进行网络的访问操作,当获取到新消息的时候进行推送,同时再设置一个alarmmanager进行下一次的轮询,当本次轮询结束的时候可以stopself结束改service。这样即使这一次的轮询失败了,也不会影响到下一次的轮询。这样就能保证推送任务不会中断。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("LongRunningService", "executed at " + new Date().
toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
}




 我们在 onStartCommand()方法里开启了一个子线程, 然后在子线程里就可以执行具体的逻辑操作了。这里简单起见,只是打印了一下当前的时间。
创建线程之后的代码就是我们刚刚讲解的 Alarm 机制的用法了,先是获取到了 AlarmManager 的实例,然后定义任务的触发时间为一小时后,再使用 PendingIntent 指定处 理定时任务的广播接收器为 AlarmReceiver,最后调用 set()方法完成设定。
显然,AlarmReceiver目前还不存在呢,所以下一步就是要新建一个 AlarmReceiver类,
并让它继承自 BroadcastReceiver,代码如下所示: 我们在 onStartCommand()方法里开启了一个子线程, 然后在子线程里就可以执行具体的逻辑操作了。这里简单起见,只是打印了一下当前的时间。
创建线程之后的代码就是我们刚刚讲解的 Alarm 机制的用法了,先是获取到了AlarmManager 的实例,然后定义任务的触发时间为一小时后,再使用 PendingIntent 指定处理定时任务的广播接收器为 AlarmReceiver,最后调用 set()方法完成设定。
显然,AlarmReceiver目前还不存在呢,所以下一步就是要新建一个 AlarmReceiver类,
并让它继承自 BroadcastReceiver,代码如下所示:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}


onReceive()方法里的代码非常简单,就是构建出了一个 Intent 对象,然后去启动 LongRunningService 这个服务。那么这里为什么要这样写呢?其实在不知不觉中,这就已经 将一个长期在后台定时运行的服务完成了。因为一旦启动 LongRunningService,就会在 onStartCommand()方法里设定一个定时任务,这样一小时后 AlarmReceiver 的 onReceive()方 法就将得到执行,然后我们在这里再次启动 LongRunningService,这样就形成了一个永久的 循环,保证 LongRunningService 可以每隔一小时就会启动一次,一个长期在后台定时运行的 服务自然也就完成了。
        另外需要注意的是,从 Android 4.4 版本开始,Alarm 任务的触发时间将会变得不准确, 有可能会延迟一段时间后任务才能得到执行。这并不是个 bug,而是系统在耗电性方面进行 的优化。系统会自动检测目前有多少 Alarm任务存在,然后将触发时间将近的几个任务放在 一起执行,这就可以大幅度地减少 CPU被唤醒的次数,从而有效延长电池的使用时间。 当然,如果你要求 Alarm任务的执行时间必须准备无误,Android仍然提供了解决方案。 使用 AlarmManager的 setExact()方法来替代 set()方法,就可以保证任务准时执行了。

你可能感兴趣的:(Android)