Android AppWidget重启重置与布局适配问题求助
问题解决方案
问题1:应用重启后Widget重置为默认布局
原因分析
today作为类成员变量在初始化时赋值,应用跨天重启会使用旧日期获取数据;同时应用重启后Widget优先显示XML默认布局,若数据获取不及时就会停留在虚拟数据。- 使用
collect持续监听Flow会导致协程长期运行,既浪费资源,还可能在多次触发更新时重复启动协程引发逻辑混乱。 - 定时更新逻辑设置为2分钟间隔,不符合“每日午夜更新”的需求,且未正确计算下一个午夜的时间点。
修复方案
- 将
today的获取移到每次数据请求的协程内部,确保始终使用当前日期。 - 改用
first()获取Flow的当前数据(而非collect持续监听),获取到数据后立即更新Widget并结束协程。 - 修正定时更新逻辑,计算次日午夜的时间作为下一次更新触发点。
- 重写
onEnabled方法,确保首次添加或重启后主动触发Widget更新。
问题2:调整Widget大小无法切换中等布局
原因分析
- 未重写
onAppWidgetOptionsChanged方法,Widget尺寸变化时不会触发布局切换逻辑。 - 精确匹配
SizeFCompat的固定值,而实际调整Widget时尺寸通常不是精确的预设值,导致无法匹配到中等布局。 - 未在Widget的配置XML中正确声明支持的尺寸范围和调整模式。
修复方案
- 重写
onAppWidgetOptionsChanged方法,在尺寸变化时主动触发布局更新。 - 修改尺寸判断逻辑,通过范围(如宽度阈值)来选择布局,而非精确匹配固定尺寸。
- 在Widget info XML中添加
resizeMode="horizontal"并声明合理的最小宽高。
修改后的完整代码
@AndroidEntryPoint class AppWidget : AppWidgetProvider() { private val job = SupervisorJob() private val coroutineScope = CoroutineScope(Dispatchers.IO + job) @Inject lateinit var repository: quoteRepository private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("MMMM") // 定义布局尺寸阈值(单位:dp),根据实际需求调整 private const val SMALL_LAYOUT_MAX_WIDTH = 180f override fun onReceive(context: Context, intent: Intent?) { super.onReceive(context, intent) if (AppWidgetManager.ACTION_APPWIDGET_UPDATE == intent?.action) { updateAllWidgets(context) } } override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { updateWidgets(context, appWidgetIds) scheduleNextMidnightUpdate(context) } override fun onAppWidgetOptionsChanged( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle? ) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) // 尺寸变化时单独更新该Widget coroutineScope.launch { updateSingleWidget(context, appWidgetManager, appWidgetId) } } override fun onEnabled(context: Context) { super.onEnabled(context) // 首次添加Widget时触发更新 updateAllWidgets(context) scheduleNextMidnightUpdate(context) } override fun onDisabled(context: Context) { super.onDisabled(context) job.cancel() // 取消定时任务 context.alarmManager.cancel(getUpdatePendingIntent(context)) } private fun updateAllWidgets(context: Context) { val appWidgetManager = AppWidgetManager.getInstance(context) val ids = appWidgetManager.getAppWidgetIds(ComponentName(context, AppWidget::class.java)) updateWidgets(context, ids) } private fun updateWidgets(context: Context, widgetIds: IntArray) { coroutineScope.launch { val today = LocalDate.now() val quote = repository.getDailyquote(today).first() val appWidgetManager = AppWidgetManager.getInstance(context) widgetIds.forEach { id -> updateSingleWidget(context, appWidgetManager, id, quote) } } } private suspend fun updateSingleWidget( context: Context, appWidgetManager: AppWidgetManager, widgetId: Int, quote: QuoteOfTheDay? = null ) { val today = LocalDate.now() val targetQuote = quote ?: repository.getDailyquote(today).first() val widgetSize = appWidgetManager.getAppWidgetOptions(widgetId).let { SizeFCompat( it.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat(), it.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat() ) } val layoutId = if (widgetSize.width <= SMALL_LAYOUT_MAX_WIDTH) { R.layout.quote_widget_small } else { R.layout.quote_widget_medium } updateWidget( context, appWidgetManager, widgetId, layoutId, targetQuote.quote.title, today.dayOfMonth.toString(), today.format(dateFormatter) ) } private fun scheduleNextMidnightUpdate(context: Context) { val activeWidgetIds = getActiveWidgetIds(context) if (activeWidgetIds.isEmpty()) return // 计算次日午夜的时间 val nextMidnight = ZonedDateTime.now() .truncatedTo(ChronoUnit.DAYS) .plusDays(1) val pendingIntent = getUpdatePendingIntent(context) context.alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextMidnight.toInstant().toEpochMilli(), pendingIntent ) } private fun getActiveWidgetIds(context: Context): IntArray { val appWidgetManager = AppWidgetManager.getInstance(context) val componentName = ComponentName(context, this::class.java) return appWidgetManager.getAppWidgetIds(componentName) } private fun getUpdatePendingIntent(context: Context): PendingIntent { val updateIntent = Intent(context, AppWidget::class.java) .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE) .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, getActiveWidgetIds(context)) return PendingIntent.getBroadcast( context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } private val Context.alarmManager: AlarmManager get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager private fun updateWidget( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, layoutId: Int, title: String?, day: String?, month: String? ) { val views = RemoteViews(context.packageName, layoutId) views.setTextViewText(R.id.quote_text, title ?: "") views.setTextViewText(R.id.date_text, day ?: "") views.setTextViewText(R.id.month_text, month?.lowercase() ?: "") views.setOnClickPendingIntent(R.id.widget_layout, getPendingIntentActivity(context)) appWidgetManager.updateAppWidget(appWidgetId, views) } private fun getPendingIntentActivity(context: Context): PendingIntent { val intent = Intent(context, MainActivity::class.java) return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } }
额外配置要求(Widget Info XML)
确保在res/xml/app_widget_info.xml中正确配置:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="160dp" android:minHeight="110dp" android:resizeMode="horizontal" android:widgetCategory="home_screen" android:initialLayout="@layout/quote_widget_small"> </appwidget-provider>
内容的提问来源于stack exchange,提问作者zkvvoob
相关产品推荐
相关产品推荐

