You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android AppWidget重启重置与布局适配问题求助

问题解决方案

问题1:应用重启后Widget重置为默认布局

原因分析

  1. today作为类成员变量在初始化时赋值,应用跨天重启会使用旧日期获取数据;同时应用重启后Widget优先显示XML默认布局,若数据获取不及时就会停留在虚拟数据。
  2. 使用collect持续监听Flow会导致协程长期运行,既浪费资源,还可能在多次触发更新时重复启动协程引发逻辑混乱。
  3. 定时更新逻辑设置为2分钟间隔,不符合“每日午夜更新”的需求,且未正确计算下一个午夜的时间点。

修复方案

  • 将today的获取移到每次数据请求的协程内部,确保始终使用当前日期。
  • 改用first()获取Flow的当前数据(而非collect持续监听),获取到数据后立即更新Widget并结束协程。
  • 修正定时更新逻辑,计算次日午夜的时间作为下一次更新触发点。
  • 重写onEnabled方法,确保首次添加或重启后主动触发Widget更新。

问题2:调整Widget大小无法切换中等布局

原因分析

  1. 未重写onAppWidgetOptionsChanged方法,Widget尺寸变化时不会触发布局切换逻辑。
  2. 精确匹配SizeFCompat的固定值,而实际调整Widget时尺寸通常不是精确的预设值,导致无法匹配到中等布局。
  3. 未在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:07:05