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

Jus*_*tin 30 multithreading android widget background-process android-8.0-oreo

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

可能的解决方案

  • 启动前台服务以更新窗口小部件.用户通知会在10s内消失.
  • 使用JobScheduler尽快安排作业.您的小部件可能会或可能不会更新一段时间.
  • 尝试在广播接收器中完成工作.锁定任何其他应用程序的UI线程.呸.
  • 尝试在小部件接收器中工作.锁定任何其他应用程序的UI线程.呸.
  • 滥用GCM以运行后台服务.很多工作和感觉hacky.

我个人并不喜欢上述任何一种解决方案.希望我错过了一些东西.

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

Com*_*are 10

您没有指出更新触发机制是什么.您似乎担心延迟("您的小部件可能会或可能不会在一段时间内更新"),因此我将假设您的关注与用户与应用小部件的交互相关联,例如点击按钮.

使用JobScheduler尽快安排作业.您的小部件可能会或可能不会更新一段时间.

这是"使用JobIntentService" 的变体,AFAIK是此类场景的推荐解决方案.

其他选择包括:

  • 使用getForegroundService()PendingIntent.有了这个,你有效地"粉红色发誓"你的服务将startForeground()在ANR时间范围内调用.如果工作时间超过几秒钟,请致电startForeground()以确保Android不会变得暴躁.这应该最小化前景通知出现的时间.而且,如果用户点击了一个按钮并且您在几秒钟后仍然忙于工作,您可能希望显示通知或以其他方式做某事让用户知道他们要求的内容仍在进行中.

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

  • 初始测试看起来很适合在广播接收器中使用goAsync()! (3认同)
  • 移动到JobIntentService后,我的窗口小部件有时没有显示更新的响应.将日志信息放入其中,我可以看到,在一种情况下,作业被延迟了7分钟,最后七分钟的所有小部件更新都被一次性显示.我接受了答案,但我必须继续尝试其他方法,因为JobIntentService显然不适用于与UI相关的任何内容. (2认同)
  • @Justin你最终坚持使用goAsync吗? (2认同)

Kas*_*mir 7

您可以使用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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

override fun onDisabled(context: Context) {
    WorkManager.getInstance(context).cancelAllWork()
}
Run Code Online (Sandbox Code Playgroud)

最后我们创建工人阶级:

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()
}
Run Code Online (Sandbox Code Playgroud)

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

您可以在此处找到示例。