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

如何在Jetpack Compose中实现图片下载至本地存储?

Jetpack Compose 实现图片下载到本地存储的方案

1. 先搞定权限配置

存储权限是基础,不同Android版本的权限规则不一样,先在AndroidManifest.xml里加对应权限:

<!-- Android 10及以下需要的写入权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<!-- Android 13+ 针对图片媒体的权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

在Compose里用rememberLauncherForActivityResult做动态权限申请,避免直接操作文件路径触发权限问题。

2. 正确用ContentResolver/MediaStore保存图片

你之前用ContentResolver报错,大概率是没按分区存储规则来操作,下面是完整可运行的实现:

第一步:写保存图片的核心函数

suspend fun saveImageToGallery(
    context: Context,
    bitmap: Bitmap,
    displayName: String = "compose_img_${System.currentTimeMillis()}.png",
    mimeType: String = "image/png"
): Uri? = withContext(Dispatchers.IO) {
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
        put(MediaStore.Images.Media.MIME_TYPE, mimeType)
        // 不同版本的存储路径配置
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        } else {
            put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/ComposeDownloads")
        }
    }

    var uri: Uri? = null
    try {
        // 先向MediaStore插入记录,拿到Uri再写入流
        uri = context.contentResolver.insert(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            contentValues
        )
        uri?.let {
            context.contentResolver.openOutputStream(it)?.use { outputStream ->
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        // 写入失败就删掉空记录
        uri?.let { context.contentResolver.delete(it, null, null) }
        uri = null
    }
    uri
}

第二步:在Compose页面调用(含权限判断)

@Composable
fun ImageSaveDemo() {
    val context = LocalContext.current
    // 替换成你实际要保存的Bitmap(比如从网络加载的图片)
    val targetBitmap = remember {
        BitmapFactory.decodeResource(context.resources, R.drawable.your_image)
    }

    // 权限申请 launcher
    val permissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            CoroutineScope(Dispatchers.Main).launch {
                val saveUri = saveImageToGallery(context, targetBitmap)
                Toast.makeText(context, if (saveUri != null) "保存成功" else "保存失败", Toast.LENGTH_SHORT).show()
            }
        } else {
            Toast.makeText(context, "需要存储权限才能保存图片", Toast.LENGTH_SHORT).show()
        }
    }

    Button(onClick = {
        // 根据系统版本判断要申请的权限
        val requiredPermission = when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> Manifest.permission.READ_MEDIA_IMAGES
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Manifest.permission.READ_EXTERNAL_STORAGE
            else -> Manifest.permission.WRITE_EXTERNAL_STORAGE
        }

        if (ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED) {
            // 权限已通过,直接保存
            CoroutineScope(Dispatchers.Main).launch {
                val saveUri = saveImageToGallery(context, targetBitmap)
                Toast.makeText(context, if (saveUri != null) "保存成功" else "保存失败", Toast.LENGTH_SHORT).show()
            }
        } else {
            // 申请权限
            permissionLauncher.launch(requiredPermission)
        }
    }) {
        Text("保存图片到相册")
    }
}

3. 常见报错排查

  • SecurityException: Permission denied:检查权限是否正确申请,Android 10+别直接写文件路径,必须走MediaStore。
  • IOException: Operation not permitted:确认openOutputStream时流没有泄漏,ContentValues里的MIME类型要和图片格式匹配。
  • 保存后找不到图片:调用MediaScannerConnection通知系统刷新媒体库:
    MediaScannerConnection.scanFile(
        context,
        arrayOf(saveUri?.path),
        arrayOf(mimeType)
    ) { _, _ -> /* 扫描完成后的逻辑 */ }
    

内容的提问来源于stack exchange,提问作者NikAn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 19:22:10