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

Jetpack Glance迁移Widget时添加StackView(基于RemoteViewsService)遇崩溃问题求助

Jetpack Glance迁移Widget时添加StackView(基于RemoteViewsService)遇崩溃问题求助

兄弟,我之前把旧的AppWidgetProvider widget迁移到Glance的时候也踩过一模一样的坑!你遇到的这个崩溃提示“setRemoteAdapter can only be used for AppWidgets”,本质原因是你把带StackView的RemoteViews当成子Composable嵌套了,而setRemoteAdapter要求这个RemoteViews必须是AppWidget的根视图,系统需要它直接关联到对应的WidgetId,嵌套的子视图根本没这个权限。

我给你梳理几个可行的解决方案,你可以根据自己的需求选:

方案一:沿用RemoteViewsService,把StackView布局作为Widget根内容

如果你想继续用旧的RemoteViewsService逻辑,那得调整代码,让带StackView的RemoteViews成为整个Glance Widget的根,不能嵌套在其他Glance布局(比如Column、Box)里。

修改后的代码大概是这样:

@Composable
private fun CardsStackView(glanceId: GlanceId) {
    val context = LocalContext.current
    val packageName = context.packageName
    val glanceManager = GlanceAppWidgetManager(context)
    // 获取对应的WidgetId,这里runBlocking没问题,因为Glance本身就是协程环境
    val widgetId = runBlocking { glanceManager.getAppWidgetId(glanceId) }

    // 直接创建根RemoteViews,设置StackView的RemoteAdapter
    val remoteViews = RemoteViews(packageName, R.layout.cards_stack).apply {
        val intent = Intent(context, CardItemService::class.java).apply {
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
            data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
        }
        setRemoteAdapter(R.id.cards, intent)
        // 记得设置空视图(如果需要的话)
        setEmptyView(R.id.cards, R.id.empty_view)
    }

    // 直接返回这个AndroidRemoteViews,不要套其他Composable
    AndroidRemoteViews(remoteViews)
}

// 你的GlanceWidget类里直接用这个根布局
class MyGlanceWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            CardsStackView(id)
        }
    }
}

另外还要确保你的CardItemService和Manifest注册是对的:

class CardItemService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        val widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
        return CardItemViewFactory(applicationContext, widgetId)
    }

    class CardItemViewFactory(private val context: Context, private val widgetId: Int) : RemoteViewsFactory {
        override fun onCreate() {}
        override fun onDataSetChanged() {}
        override fun onDestroy() {}
        override fun getCount(): Int = 5 // 替换成你的实际数据数量
        override fun getViewAt(position: Int): RemoteViews {
            val itemView = RemoteViews(context.packageName, R.layout.card_item)
            itemView.setTextViewText(R.id.card_title, "卡片 $position")
            // 设置item点击的填充意图
            val fillInIntent = Intent().apply { putExtra("POSITION", position) }
            itemView.setOnClickFillInIntent(R.id.card_root, fillInIntent)
            return itemView
        }
        override fun getLoadingView(): RemoteViews? = null
        override fun getViewTypeCount(): Int = 1
        override fun getItemId(position: Int): Long = position.toLong()
        override fun hasStableIds(): Boolean = true
    }
}

Manifest里注册Service:

<service
    android:name=".CardItemService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

方案二:改用Glance原生的LazyColumn(更推荐)

其实Glance本身提供了LazyColumn组件,完全可以替代StackView,而且不需要依赖RemoteViewsService,代码更简洁,也更符合Glance的Compose开发模式。

示例代码:

@Composable
private fun CardsList(glanceId: GlanceId) {
    LazyColumn(
        modifier = GlanceModifier.fillMaxSize().padding(8.dp)
    ) {
        // 替换成你的实际数据源
        items(5) { position ->
            CardItem(position)
        }
    }
}

@Composable
private fun CardItem(position: Int) {
    Box(
        modifier = GlanceModifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
            .background(Color.LightGray)
            .clickable {
                // 点击跳转到你的Activity
                actionStartActivity<MainActivity>()
            }
    ) {
        Text(
            text = "卡片 $position",
            modifier = GlanceModifier.padding(16.dp)
        )
    }
}

这种方式不需要写复杂的RemoteViewsService,数据更新也可以通过Glance的状态管理来处理,维护起来方便很多。

补充说明

如果你的Widget需要混合其他UI(比如顶部标题栏+StackView),那建议全部用RemoteViews来构建整个布局,不要混合Glance的Composable和AndroidRemoteViews,否则还是会遇到嵌套的问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:25:28