use*_*527 5 android watchdog job-scheduling android-jobscheduler android-workmanager
我的 Android 应用程序中需要一个组件,最好将其描述为看门狗,即每 30 分钟 +/- 5 分钟执行一次并断言仍满足某个条件的函数。还必须在设备重新启动后执行看门狗,而用户此后没有明确打开应用程序。应用程序的安装也必须如此。即使在安装后未明确打开应用程序,也必须安排看门狗定期执行。
我知道使用WorkManager是最好的或“现代”的方式。无需WorkManager我为不同的 API 级别编写单独的代码,即BroadcastReceiver用于 API 级别 <27 和JobScheduler更高 API 级别的设备。WorkManager应该抽象出这些差异。
但我不明白在哪里打电话WorkManager.getInstance().enqueue( myWatchdogRequest );。使用任何主要活动的回调(即onCreate和类似的)都不是正确的地方,因为我不能依赖正在创建的活动。
我希望除了以编程方式排队作业之外,还应该有一种方法如何在清单中声明这些作业,从而将它们通知给系统(类似于老式的BroadcastReceiver)。实际上,JobScheduler如果我决定使用这种方法,我也会遇到同样的问题。
我在哪里排队WorkRequest“全局”?
use*_*527 13
在第一部分中,我只是将解决方案作为代码片段呈现,没有太多解释。在第二部分中,我详细阐述了解决方案,解释了为什么它不是一个精确的解决方案,而是最好的解决方案,并指出了 Google 文档中的一些错误,这些错误导致我首先提出了这个问题。
解决方案
每 30 分钟运行一次并具有 10 分钟灵活性的实际工作线程:
public class WatchDogWorker extends Worker {
private static final String uniqueWorkName = "my.package.name.watch_dog_worker";
private static final long repeatIntervalMin = 30;
private static final long flexIntervalMin = 10;
public WatchDogWorker( @NonNull Context context, @NonNull WorkerParameters params) {
super( context, params );
}
private static PeriodicWorkRequest getOwnWorkRequest() {
return new PeriodicWorkRequest.Builder(
WatchDogWorker.class, repeatIntervalMin, TimeUnit.MINUTES, flexIntervalMin, TimeUnit.MINUTES
).build();
}
public static void enqueueSelf() {
WorkManager.getInstance().enqueueUniquePeriodicWork( uniqueWorkName, ExistingPeriodicWorkPolicy.KEEP, getOwnWorkRequest() );
}
public Worker.Result doWork() {
// Put the actual code of the watchdog that needs to be run every 30mins here
return Result.SUCCESS;
}
}
Run Code Online (Sandbox Code Playgroud)
注意: a) 由于这个 worker 需要以相同的方式在两个不同的执行点(见下文)注册以进行调度,我决定WatchDogWorker应该“知道”如何将自己排入队列。因此它提供了静态方法getOwnWorkRequest和enqueueSelf. b) 私有的、静态的常量只需要一次,但使用常量可以避免代码中的幻数,并为数字赋予语义含义。
要WatchDogWorker在设备启动后排队调度,需要以下广播接收器:
public class BootCompleteReceiver extends BroadcastReceiver {
public void onReceive( Context context, Intent intent ) {
if( intent.getAction() == null || !intent.getAction().equals( "android.intent.action.BOOT_COMPLETED" ) ) return;
WatchDogWorker.enqueueSelf();
}
}
Run Code Online (Sandbox Code Playgroud)
本质上,整个魔术是一个单行并调用WatchDogWorker.enqueueSelf. 广播接收器应该在启动后调用一次。为此,必须声明广播接收器AndroidManifest.xml,以便 Android 系统知道接收器并在启动时调用它:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="...">
...
<application>
...
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
Run Code Online (Sandbox Code Playgroud)
然而,这还不够。如果用户刚刚安装了应用程序,我们不想等待下一次重启直到看门狗第一次被调度,但我们希望它尽快被调度。因此,WatchDogWorker如果创建了主要活动,也会入队。
public class MainActivity extends AppCompatActivity {
...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// Schedule WatchDogWorker (after a fresh install we must not rely on the BootCompleteReceiver)
WatchDogWorker.enqueueSelf();
}
}
Run Code Online (Sandbox Code Playgroud)
注意:此解决方案可能会WatchDogWorker.enqueueSelf多次调用该方法。然而,enqueueSelf在内部调用enqueueUniquePeriodicWork有ExistingPeriodicWorkPolicy.KEEP。因此,随后对 的调用enqueueSelf是空操作并且不会造成伤害。
警告:所提供的解决方案只是一个 95% 的解决方案。如果用户在安装后从不启动应用程序,即从不创建 Activity,WatchDogWorker从不入队,也从不运行。甚至,如果设备最终在未来的某个时间点重新启动(但应用程序从未启动过),则永远不会收到“启动完成”意图,WatchDogWorker也不会入队。这种情况没有解决方法。(见下一章。)
其他背景信息
导致我提出这个问题的第一个问题是,如果设备已在不依赖要创建的活动的情况下重新启动,则如何使工作人员入队。我确实了解广播接收器,尤其是BOOT_COMPLETED-intent。但根据官方 Android 文档,从 Android 8 开始,几乎所有广播接收器都被彻底禁用。这项测量是 Google 改进电源管理尝试的一部分。在过去,据称广播接收器被许多技术水平较低的开发人员滥用,以做一些本应以其他方式做得更好的疯狂事情。(简单的例子:滥用AlarmManager和相应的广播接收器每 500 毫秒唤醒您的应用程序,只是为了检查您的服务器上是否有可用更新。)谷歌的对策是简单地切断这些广播接收器。更准确地说,来自文档的引用:
从 Android 8.0 [...] 开始,系统对清单声明的接收器施加 [...] 限制。[...] 您不能使用清单为大多数隐式广播(不专门针对您的应用程序的广播)声明接收器。当用户正在使用您的应用程序时,您仍然可以使用上下文注册接收器。
两个方面很重要: 该限制适用于隐式意图。不幸的是,根据 docs,BOOT_COMPLETED意图是隐含的意图。其次,可以克服此限制,但只能通过编程方式或换句话说,通过您的活动的某些已执行代码来克服。再次不幸的是,如果实际目标不是依赖于用户启动的活动,这不是一种解决方法。
这是我以为我迷路的地方。但是,上面的规则也有一些例外,BOOT_COMPLETED属于这种例外。令人惊讶的是,正确的文档页面被称为“隐式广播异常”,更令人惊讶的是不是很容易找到。无论如何,它说
ACTION_LOCKED_BOOT_COMPLETED、ACTION_BOOT_COMPLETED
豁免是因为这些广播仅在第一次启动时发送一次,并且许多应用程序需要接收此广播来安排作业、警报等。
这正是这里所需要的,并且已经被谷歌注意到了。总结:是的,大多数隐式广播接收器已被放弃,但不是全部,并且BOOT_COMPLETED是其中之一。它仍然有效,并且(希望)将来会有效。
第二个问题仍然存在:如果用户从不重启设备并且在安装后从未启动应用程序至少一次,WatchDogServer则永远不会入队。(这是我对问题的解决方案中缺少的 5%。)有一个ACTION_PACKAGE_ADDED-intent,但它在这里没有帮助,因为已添加的特定应用程序从未收到其“自己的”意图。
无论如何,上述缺点无法克服,它是 Google 反恶意软件活动的一部分。(不幸的是,我失去了参考链接。)这是一种实用的解决方案,可以阻止恶意软件静默建立后台任务。安装包后,它仍处于某种“半安装”状态。(谷歌称之为“暂停”,但不要将其与活动的暂停状态混淆。这里,这是指整个包的状态。)该包一直保持这种状态,直到用户手动启动android.intent.action.MAIN来自启动器的-intent的主要活动至少一次。只要包处于“暂停”状态,它也不会收到任何广播意图。在这种特殊情况下,BOOT_COMPLETED- 下次启动时未收到-intent。总结一下:你不能编写一个只包含后台任务的应用程序,即使这是你应用程序的全部目的。您的应用需要至少向用户显示一次的 Activity。否则什么都不会运行。巧合的是,由于法律原因,大多数国家/地区的大多数应用程序无论如何都需要某种法律说明或数据政策,因此您可以使用该活动来静态显示。在 Playstore 的应用程序描述中,要求用户启动应用程序(甚至可能阅读您的文本)以完成安装。
| 归档时间: |
|
| 查看次数: |
2767 次 |
| 最近记录: |