如何在 Android 8.0 - Oreo - API 26 中正确更新小部件

How To Properly Update A Widget In Android 8.0 - Oreo - API 26

假设我有一个应用程序的小部件,其 targetSDKVersion 设置为 26。此小部件需要 100 毫秒到 10 秒的更新时间。大部分时间都在 1 秒以下。在 Android O 之前,如果在我的 AppWidgetProvider 上调用了 onUpdate(),我可以启动后台服务来更新这个小部件。但是,如果您尝试该行为,Android O returns 一个 IllegalStateException。启动前台服务的明显解决方案似乎是对 99% 的时间在 10 秒内完成的事情的极端措施。

可能的解决方案

我个人不喜欢上述任何一种解决方案。希望我遗漏了什么。

(更令人沮丧的是我的应用程序已经被系统调用onUpdate()加载到内存中。我没有看到如何将我的应用程序加载到内存中以调用onUpdate(),但是没有给我的应用程序1s从 UI 线程更新小部件可以节省任何电池寿命。)

你没有说明更新触发机制是什么。您似乎担心延迟 ("Your widget may or may not get updated for a while"),所以我假设您的担心与用户与应用程序小部件的交互有关,例如点击按钮。

Use JobScheduler to schedule a job as quickly as possible. Your widget may or may not get updated for a while.

这是 "use JobIntentService" 的变体,AFAIK 是此类场景的推荐解决方案。

其他选项包括:

  • 使用 getForegroundService()PendingIntent。有了这个,您实际上 "pinky swear" 您的服务将在 ANR 时间范围内调用 startForeground()。如果工作需要的时间超过几秒钟,请致电 startForeground() 以确保 Android 不会变得胡思乱想。这应该最大限度地减少前台通知出现的次数。而且,如果用户点击了一个按钮,几秒钟后你仍在忙于工作,你可能 想要 显示通知或以其他方式让用户知道他们问了什么仍在进行中。

  • BroadcastReceiver 上使用 goAsync(),以便在不占用主应用程序线程的情况下在接收器的上下文中工作。我还没有用 Android 8.0+ 尝试过,所以 YMMV。

您可以使用 WorkManager 来更新小部件。在 API 14+ 的设备上使用 WorkManager。你需要覆盖 fun onReceive(context: Context?, intent: Intent?) 像这样:

val ACTION_AUTO_UPDATE : String = "AUTO_UPDATE";

override fun onReceive(context: Context?, intent: Intent?) {
    super.onReceive(context, intent)
    if(intent?.action.equals(ACTION_AUTO_UPDATE))
    {
        val appWidgetManager = AppWidgetManager.getInstance(context)
        val thisAppWidgetComponentName = ComponentName(context!!.getPackageName(), javaClass.name)
        val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName)
        for (appWidgetId in appWidgetIds) {
            // update widget
        }
    }
}

并且您应该创建 PeriodicWorkRequest。您必须用于重复工作。定期工作的最短间隔为 15 分钟。我们在启用小部件时将 periodicWork 排入队列:

override fun onEnabled(context: Context) {
    val periodicWorkRequest = PeriodicWorkRequest.Builder(YourWorker::class.java, 15, TimeUnit.MINUTES).build()
    WorkManager.getInstance(context).enqueueUniquePeriodicWork("YourWorker", ExistingPeriodicWorkPolicy.REPLACE,periodicWorkRequest)
}

并在禁用小部件时取消它:

override fun onDisabled(context: Context) {
    WorkManager.getInstance(context).cancelAllWork()
}

最后我们创建了worker class:

class YourWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
var context : Context? = null

init {
    context = ctx
}

override fun doWork(): Result {
    val alarmIntent = Intent(context, YourWidget::class.java)
    alarmIntent.action = YourWidget().ACTION_AUTO_UPDATE
    context?.sendBroadcast(alarmIntent)
    return Result.success()
}

如果你想使用 WorkerManager,你添加到 build.gradle 实现 'androidx.work:work-runtime:2.3.1'

您可以找到示例 here