前面在"Android小部件APP Widget开发"中详细介绍了
APP Widget
的基本使用,并且知道了设置AppWidgetProviderInfo
的updatePeriodMillis
可以指定小部件的定时刷新时间,然后在BroadcastReceiver
中更新我们的小部件。这里可能就会遇到两个小问题。
BroadcastReceiver
的onReceive()
需要在10秒内执行完毕,不然会有ANR
异常的提示。updatePeriodMillis
的最小间隔为30分钟,无法设置更小的间隔。为了解决这两个问题我们可以使用
WorkManager
进行小部件的更新,WorkManager
可以灵活可靠的调度和执行异步任务,而且可以设置更小的时间间隔。可以降低设备的工作负荷和耗电。这种后台持续性任务调度的方式是官方所提倡的。具体的使用可以参考这篇文章"WorkManager介绍以及用法"
接下来我会介绍基于WorkManager来更新小部件的方式。
创建更新小部件的Worker
这里我用了CoroutineWorker
,通过挂起函数getTimeStr()
获取数据,然后更新UI,具体逻辑可以看注释。
kotlin
class WidgetUpdaterWorker(private val appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val provider = ComponentName(appContext, HelloWidgetProvider::class.java)
val remoteViews = RemoteViews(appContext.packageName, R.layout.hello_widget_layout)
return try {
//获取UI数据
val timeStr = getTimeStr()
//设置数据
remoteViews.setTextViewText(R.id.text, timeStr)
//更新小部件
AppWidgetManager.getInstance(appContext)
.updateAppWidget(provider, remoteViews)
//成功
Result.success()
} catch (e: IOException) {
//网络等异常进行重试
Result.retry()
}
}
private suspend fun getTimeStr(): String {
//假装是网络延迟
delay(500)
// 获取当前时间
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
return sdf.format(Date())
}
}
使用WorkManager进行更新
修改之前的onUpdate
逻辑,为了简单演示这里直接enqueue
就行。
kotlin
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
WorkManager.getInstance(context.applicationContext)
.enqueue(
OneTimeWorkRequestBuilder<WidgetUpdaterWorker>()
.build()
)
}
运行程序看看
上面能看到,小部件一直在initialLayout
和更新后的布局之间切换。
使用WorkManager更新小部件的问题
因为能看到时间一直是在变的,所以这里猜测小部件一直在更新?
在onUpdate
加个日志看看
kotlin
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
Log.d(TAG,"onUpdate")
·····
}
能看到,的确频繁的被调用。
经过一段Google之后终于明白了原因。
参考:issuetracker.google.com/issues/2410...
大概原因如下,画了个图。
解决方法
在Application
创建的时候enqueue
一个InitialDelay
为10年的Worker
。
这样做主要是阻断死循环中的 "WorkManager
没有待执行的任务"这一步。
kotlin
WorkManager.getInstance(applicationContext)
.enqueueUniqueWork(
"not_executed_work",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<WidgetUpdaterWorker>()
.setInitialDelay(365 * 10, TimeUnit.DAYS)
.build()
)
不一定是用WidgetUpdaterWorker
,随便一个Worker
都行。
这里使用的是enqueueUniqueWork
,目的是为了防止重复添加。
折中的解决方法
这个并不是完美的方案, 但是感觉可以接受的。
解决方案也就是下面会介绍的,在小部件enable
的时候添加定时更新的WorkRequest
。
因为添加了periodicUpdateRequest
,所以不会存在"WorkManager
没有待执行的任务"的情况。
不过这个方式小部件在第一次添加的时候还是会刷新多一次,因为只要一次添加WorkRequest
就会触发onUpdate
,貌似目前来看无解。
使用WorkManager定时更新
如果希望小部件在APP被杀死后还能进行更新,可以使用WorkManager.enqueueUniquePeriodicWork()
添加一个周期任务。
kotlin
//第一个小部件添加的时候
override fun onEnabled(context: Context) {
//创建WorkRequest,15分钟一个周期
val periodicUpdateRequest =
PeriodicWorkRequestBuilder<WidgetUpdaterWorker>(
15, TimeUnit.MINUTES,
5, TimeUnit.MINUTES
).build()
//添加UniqueWork,不用怕会重复添加,这里的TAG也可以用来取消任务
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
TAG,
ExistingPeriodicWorkPolicy.KEEP,
periodicUpdateRequest
)
}
//最后一个小部件被删除的时候
override fun onDisabled(context: Context) {
//根据TAG取消掉任务
WorkManager.getInstance(context).cancelUniqueWork(TAG)
}
上面代码展示了在onEnabled
的时候添加一个十五分钟的周期任务,在onDisabled
把这个周期性任务取消。
运行程序看看
添加小部件
十五分钟后
这里能看到在17分钟30秒
后重新更新了一遍,并没有准时的在15分,原因是系统会考虑到电池优化的原因,运行结果是符合预期的。
好了到这里就介绍完毕了,如果想了解APP Widget
和WorkManager
可以参考我之前的文章。