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

Kotlin中如何将Glide加载的图片下载保存到设备内部存储

RecyclerView列表项Glide图片下载保存实现方案

前置说明

  • 以下逻辑直接填充到holder.downloadBtn.setOnClickListener即可,不需要改动现有Glide加载图片的代码
  • 提供两种存储位置可选:应用内部私有存储(无权限要求)、系统公共图片目录(相册可见,高版本无权限要求)
  • 自动复用Glide已有缓存,不会重复发起网络请求下载已加载过的图片

具体实现代码

holder.downloadBtn.setOnClickListener {
    val currentImgUrl = abstractList[position].abstract.toString()
    // 临时禁用按钮防止重复点击
    it.isEnabled = false

    // 绑定item生命周期,列表滑动回收item时自动取消任务,避免内存泄漏
    holder.itemViewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
        val saveResult = runCatching {
            // 通过Glide拉取原始图片Bitmap
            val targetBitmap = Glide.with(mContext)
                .asBitmap()
                .load(currentImgUrl)
                .submit()
                .get()

            // -------------------------- 存储位置二选一即可 --------------------------
            // 选项1:存储到应用内部私有目录(无需任何权限,仅本应用可访问,卸载应用自动删除)
            val saveDir = mContext.getDir("abstract_images", Context.MODE_PRIVATE)
            val saveFileName = "abstract_${System.currentTimeMillis()}.jpg"
            val targetFile = File(saveDir, saveFileName)
            FileOutputStream(targetFile).use { outputStream ->
                targetBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
                outputStream.flush()
            }
            targetFile.absolutePath

            /*
            // 选项2:存储到系统公共Pictures目录(相册可查看,所有应用可访问,卸载不删除)
            // Android 10及以上无需存储权限,Android 9及以下需要动态申请WRITE_EXTERNAL_STORAGE权限
            val contentValues = ContentValues().apply {
                put(MediaStore.Images.Media.DISPLAY_NAME, "abstract_${System.currentTimeMillis()}.jpg")
                put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/AbstractGallery")
                    put(MediaStore.Images.Media.IS_PENDING, 1)
                }
            }
            val contentResolver = mContext.contentResolver
            val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
            contentResolver.openOutputStream(insertUri).use { outputStream ->
                targetBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream!!)
                outputStream.flush()
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                contentValues.clear()
                contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
                contentResolver.update(insertUri, contentValues, null, null)
            }
            insertUri.toString()
            */
        }

        // 切回主线程更新UI
        withContext(Dispatchers.Main) {
            holder.downloadBtn.isEnabled = true
            saveResult.onSuccess { savePath ->
                Toast.makeText(mContext, "图片保存成功:$savePath", Toast.LENGTH_SHORT).show()
            }.onFailure { error ->
                Toast.makeText(mContext, "保存失败:${error.message}", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

必要配置

  • 项目需要引入lifecycle ktx依赖才能使用lifecycleScope,在模块级build.gradle添加依赖:
dependencies {
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
}
  • 如果选择公共目录存储且需要适配Android 9(API 28)及以下版本,需要在AndroidManifest.xml添加权限声明,并在下载前动态申请权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

注意点

  • 不要在主线程调用Glide的submit().get(),会阻塞UI导致卡顿
  • 文件名建议使用唯一标识(时间戳、图片唯一ID),避免同名文件被覆盖
  • Glide的缓存机制会自动命中已经加载过的图片,不会重复消耗流量下载

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 08:51:34