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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:15:12