即使手机睡眠,如何保持一个服务运行?

51

我在我的应用程序中有一个服务,旨在每10分钟运行一次。它基本上检查我们的服务器是否正常运行,并通知用户任何问题。我为我们公司内部使用创建了这个应用程序。

我的同事在长周末使用该应用程序时注意到,当设备进入睡眠状态时,没有进行任何检查。我认为Service应该在后台持续运行,直到在我的代码中明确调用 stopService()

因此,我的目标是使服务在用户按下应用程序的关闭按钮或杀死进程之前持续运行。

我听说过一种叫做 WakeLock的东西,它可以防止屏幕关闭,但这不是我想要的。然后我听说了另一种叫做partial WakeLock的东西,即使设备处于睡眠状态,它也可以让CPU保持运行。后者听起来更接近我所需要的。

我该如何获得这个WakeLock?何时释放它?还有其他方法可以解决这个问题吗?


确实,您需要一个部分唤醒锁。请查看Android PowerManager API以获取示例。当用户点击“关闭”按钮时,请释放它。 - THelper
我也这么想,但是关于WakeLock最大的问题似乎是电池消耗。我猜部分WakeLock在这方面不会像“完全”WakeLock那样糟糕。但我可能错了。 - PaulG
1
如果有人感兴趣,可以在以下链接找到有关“sleep”进程的更多信息:https://dev59.com/iW435IYBdhLWcg3w9E9Y - PaulG
3个回答

61

注意:本帖已更新以包含 Android Lollipop 发布的 JobScheduler API。下面仍然是一种可行的方式,但如果您的目标是 Android Lollipop 及以上版本,则可以被视为已弃用。查看后半部分以获取 JobScheduler 的替代方案。

执行重复任务的一种方法如下:

  • 创建一个名为 AlarmReceiver 的类

  • public class AlarmReceiver extends BroadcastReceiver 
    {
        @Override
        public void onReceive(Context context, Intent intent) 
        {
            Intent myService = new Intent(context, YourService.class);
            context.startService(myService);
        }
    }
    

    假设YourService是您的服务;-)

    如果您需要为您的任务获取唤醒锁,则建议从 WakefulBroadcastReceiver 扩展。在这种情况下,请不要忘记在Manifest中添加 WAKE_LOCK 权限!

    • 创建一个挂起的Intent

    要启动您的定期轮询,请在您的活动中执行此代码:

    Intent myAlarm = new Intent(getApplicationContext(), AlarmReceiver.class);
    //myAlarm.putExtra("project_id", project_id); //Put Extra if needed
    PendingIntent recurringAlarm = PendingIntent.getBroadcast(getApplicationContext(), 0, myAlarm, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager alarms = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    Calendar updateTime = Calendar.getInstance();
    //updateTime.setWhatever(0);    //set time to start first occurence of alarm 
    alarms.setInexactRepeating(AlarmManager.RTC_WAKEUP, updateTime.getTimeInMillis(), AlarmManager.INTERVAL_DAY, recurringAlarm); //you can modify the interval of course
    

    这段代码设置了一个可取消的pendingIntentalarmalarmManager将重复执行recurringAlarm(第三个参数)每天一次,但是执行时间是不精确的,大约在间隔时间之后唤醒CPU而不是精确地唤醒(它让操作系统选择最佳时间,从而减少电池消耗)。第一次启动闹钟(和服务)的时间将是您选择的updateTime时间。

    • 最后但同样重要的:以下是如何停止重复闹钟

    Intent myAlarm = new Intent(getApplicationContext(), AlarmReceiver.class);
    //myAlarm.putExtra("project_id",project_id); //put the SAME extras
    PendingIntent recurringAlarm = PendingIntent.getBroadcast(getApplicationContext(), 0, myAlarm, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager alarms = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarms.cancel(recurringAlarm);
    
    这段代码创建了一个现有闹钟的副本,并告诉alarmManager取消所有这种类型的闹钟。

    • 当然,在Manifest中也有一些需要处理的内容:

    包含这两行代码。

      < receiver android:name=".AlarmReceiver"></receiver>
      < service android:name=".YourService"></service>
    

    <application> 标签内部。如果没有它,系统不会接受服务重复闹钟的启动。


    从 Android Lollipop 发布开始,有一种新的优雅解决此任务的方法。这也使得仅在满足某些条件(如网络状态)时执行操作更加容易。

    // wrap your stuff in a componentName
    ComponentName mServiceComponent = new ComponentName(context, MyJobService.class);
    // set up conditions for the job
    JobInfo task = JobInfo.Builder(mJobId, mServiceComponent)
       .setPeriodic(mIntervalMillis)
       .setRequiresCharging(true) // default is "false"
       .setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED) // Parameter may be "ANY", "NONE" (=default) or "UNMETERED"
       .build();
    // inform the system of the job
    JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    jobScheduler.schedule(task);
    

    你也可以通过 setOverrideDeadline(maxExecutionDelayMillis) 来设置任务的截止时间。

    要取消这样的任务,只需调用jobScheduler.cancel(mJobId);jobScheduler.cancelAll();即可。


1
如果您能添加一些解释最后两个代码块的作用,我会点赞这个答案。 - Octavian Helm
不用谢。"not always on target" 是有意设计的:虽然有一种替代方法可以使用 ".setInexactRepeating(...)",但是如果你让 CPU 睡得更久一些,并让闹钟与其他事情同时发生,那么它会更省电。 - stefan
2
好的解释,但您仍然需要一个部分唤醒锁来防止启动的服务“睡着”吗?如果您查看此实现,您会发现它也使用了一个部分唤醒锁加闹钟的方法。 - THelper
1
我认为在这种情况下不需要额外的wakelock。这里只是进行一些数据拉取,因此服务的寿命应该很短,应该能够快速完成其工作。 - stefan
我有一个定时器,每16分钟运行一次,检查设备时间是否已更改。但当设备处于睡眠状态时,定时器不会运行。 - Sagar Nayak
显示剩余8条评论

5

如果从一开始构建此应用程序,我会建议使用服务器端组件(是的,还需要监控!)并发送推送通知,轮询永远不是可靠的解决方案。


1
好主意,但有些人没有这个选项。在我的情况下,它可以工作,因为服务器在我们办公室,我可以访问它们,但如果您需要从外部源更新数据每10分钟怎么办?我仍然想知道如何保持服务运行。感谢回复。 - PaulG
当然可以,只是我之前曾试图推动这个项目,但最终决定不再与操作系统(尤其是iOS)对抗,而是“正确地”实现它(尽管这需要更多的基础设施开销)。 - Lee Hambley
我在这里发布了一个相当干净巧妙的解决方案。希望能对你有所帮助。 https://dev59.com/MWox5IYBdhLWcg3wsGaC#43555050 - user2288580

3
以下是翻译内容:

根据Android文档中的Doze模式(https://developer.android.com/training/monitoring-device-state/doze-standby),会发生以下情况:

系统忽略唤醒锁。

系统不允许JobScheduler运行。

除非它们在setAndAllowWhileIdle()或setExactAndAllowWhileIdle() 中,否则Android也会忽略AlarmManager。

网络访问被暂停。

因此,唯一的方法是使用高优先级的FCM或具有setAndAllowWhileIdle()或setExactAndAllowWhileIdle()的AlarmManager。


萨巴,你知道安卓服务在待机模式下是否也被暂停了吗?我认为是的。所有线程都应该被暂停,但我想听听你的意见。假设我们有一个正在执行大量工作但与网络无关的安卓服务,在待机模式下这个服务会被暂停吗? - j2emanue
1
是的,它们将被暂停。 - Saba
似乎前台服务在待机模式下仍然保持活动状态。网络操作停止,但如果声明为前台,则可以继续工作。这个异常很有趣。https://dev59.com/4lMI5IYBdhLWcg3wLYQu - j2emanue
请注意,这也取决于安卓版本。 - Saba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接