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

时间:2017-09-12 14:36:45

标签: android multithreading 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以运行后台服务。很多工作,感觉很烦。

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

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

3 个答案:

答案 0 :(得分:10)

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

  

使用JobScheduler尽快安排作业。您的小部件可能会暂时更新或不会更新。

这是“使用JobIntentService”的变体,AFAIK是此类情景的推荐解决方案。

其他选项包括:

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

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

答案 1 :(得分:2)

您可以使用WorkManager更新小部件。在具有API 14+的设备上使用WorkManager。您需要像这样重写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()
}

最后,我们创建工作者类:

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

答案 2 :(得分:0)

另一种方法是使用WorkManager。是的,它仍然是Alpha,但是将targetSDKVersion设置为28的android.arch.work:work-runtime:1.0.0-alpha10似乎非常稳定。因此,您应该在onUpdate()中启动OneTimeWorkRequest,稍后再更新该小部件。

@Override
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds) {
    super.onUpdate(context, wMgr, appWidgetIds);

    Data.Builder data_builder = new Data.Builder();
    data_builder.putIntArray(UpdaterWidgets.PARAM_WIDGET_IDS, appWidgetIds);
    OneTimeWorkRequest widgetWork =
            new OneTimeWorkRequest.Builder(UpdaterWidgets.class)
                    .setInputData(data_builder.build())
                    .build();
    WorkManager.getInstance().enqueue(widgetWork);
}