如何在 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 秒内完成的事情的极端措施。
可能的解决方案
- 启动前台服务来更新小部件。用 10 秒后就会消失的通知惹恼用户。
- 使用 JobScheduler 尽快安排作业。您的小部件可能会或可能不会更新一段时间。
- 尝试在广播接收器中完成这项工作。锁定任何其他应用程序的 UI 线程。呸
- 尝试在小部件接收器中工作。锁定任何其他应用程序的 UI 线程。呸
- 滥用 GCM 获取后台服务 运行。很多工作,感觉很老套。
我个人不喜欢上述任何一种解决方案。希望我遗漏了什么。
(更令人沮丧的是我的应用程序已经被系统调用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。
假设我有一个应用程序的小部件,其 targetSDKVersion 设置为 26。此小部件需要 100 毫秒到 10 秒的更新时间。大部分时间都在 1 秒以下。在 Android O 之前,如果在我的 AppWidgetProvider 上调用了 onUpdate(),我可以启动后台服务来更新这个小部件。但是,如果您尝试该行为,Android O returns 一个 IllegalStateException。启动前台服务的明显解决方案似乎是对 99% 的时间在 10 秒内完成的事情的极端措施。
可能的解决方案
- 启动前台服务来更新小部件。用 10 秒后就会消失的通知惹恼用户。
- 使用 JobScheduler 尽快安排作业。您的小部件可能会或可能不会更新一段时间。
- 尝试在广播接收器中完成这项工作。锁定任何其他应用程序的 UI 线程。呸
- 尝试在小部件接收器中工作。锁定任何其他应用程序的 UI 线程。呸
- 滥用 GCM 获取后台服务 运行。很多工作,感觉很老套。
我个人不喜欢上述任何一种解决方案。希望我遗漏了什么。
(更令人沮丧的是我的应用程序已经被系统调用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。