如何在Jetpack Glance中实现后台绘制图片并更新Widget?
实现Jetpack Glance后台绘图画并更新Widget的方案
核心思路是:在后台生成Bitmap并存储到Widget可访问的位置,再触发Glance Widget更新加载该图片。以下是具体步骤:
1. 配置Widget点击触发后台任务
在Glance Widget的布局代码中,给可点击区域绑定WorkManager任务,触发后台绘图逻辑:
@Composable fun MyGlanceWidgetContent() { Box(modifier = GlanceModifier.fillMaxSize().clickable(actionRunWorker<DrawImageWorker>())) { // 当前展示的内容,比如占位图 Image( provider = ImageProvider(R.drawable.placeholder), contentDescription = "Placeholder", modifier = GlanceModifier.fillMaxSize() ) } }
2. 后台绘制Bitmap
创建WorkManager Worker类,在后台完成Bitmap绘制。这里提供两种绘制方式:
方式一:直接用Canvas绘制
class DrawImageWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) // 绘制自定义内容,比如文字、图形 val paint = Paint().apply { color = Color.RED textSize = 48f textAlign = Paint.Align.CENTER } canvas.drawText("后台绘制的内容", 200f, 200f, paint) // 保存Bitmap并更新Widget saveBitmapAndUpdateWidget(bitmap) return Result.success() } }
方式二:渲染布局文件到Bitmap
如果需要复杂的UI布局,可以把布局文件渲染成Bitmap:
private fun inflateLayoutToBitmap(context: Context): Bitmap { val view = LayoutInflater.from(context).inflate(R.layout.custom_widget_layout, null) view.measure(View.MeasureSpec.makeMeasureSpec(400, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(400, View.MeasureSpec.EXACTLY)) view.layout(0, 0, view.measuredWidth, view.measuredHeight) val bitmap = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) view.draw(canvas) return bitmap }
3. 保存Bitmap到Widget可访问位置
Widget进程无法直接访问App私有文件,所以把Bitmap存入MediaStore,获取可访问的Content URI:
private suspend fun saveBitmapAndUpdateWidget(bitmap: Bitmap) { val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "widget_draw_${System.currentTimeMillis()}.png") put(MediaStore.Images.Media.MIME_TYPE, "image/png") put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyWidgetImages") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.Images.Media.IS_PENDING, 1) } } val resolver = applicationContext.contentResolver val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return resolver.openOutputStream(uri)?.use { outputStream -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.put(MediaStore.Images.Media.IS_PENDING, 0) resolver.update(uri, contentValues, null, null) } // 更新Widget,传递图片URI MyGlanceWidget().update(applicationContext, GlanceId(applicationContext.packageName + ".widget")) { preferences.edit().putString("widget_image_uri", uri.toString()).apply() } }
4. Glance Widget加载绘制好的图片
修改Widget内容代码,读取存储的URI并加载图片:
@Composable fun MyGlanceWidgetContent() { val prefs = currentState<Preferences>() val imageUri = prefs.getString("widget_image_uri", null) Box(modifier = GlanceModifier.fillMaxSize().clickable(actionRunWorker<DrawImageWorker>())) { if (imageUri != null) { Image( provider = ImageProvider(Uri.parse(imageUri)), contentDescription = "Drawn content", modifier = GlanceModifier.fillMaxSize() ) } else { Image( provider = ImageProvider(R.drawable.placeholder), contentDescription = "Placeholder", modifier = GlanceModifier.fillMaxSize() ) } } }
注意事项
- 权限:Android 10及以上无需WRITE_EXTERNAL_STORAGE权限,Android 9及以下需要在Manifest中声明该权限并请求运行时权限。
- 图片清理:定期清理生成的图片文件,避免占用过多存储。
- Worker约束:可以给WorkManager添加约束,比如只在设备充电或空闲时执行,避免影响用户体验。
内容的提问来源于stack exchange,提问作者Kitesurfer
相关产品推荐
相关产品推荐

