Android Coil3图片解码Bitmap后存入磁盘,再次加载读磁盘Bitmap缓存

Android Coil3图片解码Bitmap后存入磁盘,再次加载读磁盘Bitmap缓存

在Android中使用Coil3库实现图片缓存与加载的优化方案。通过自定义ImgCache类实现了Bitmap的磁盘缓存读写功能,包括readBmpDiskCache读取缓存和writeBmpDiskCache写入缓存方法。同时提供了decodeLargeImage方法用于处理大图解码,支持按需缩放以减少内存占用。还展示了如何配置ImageLoader,包括初始化内存缓存和磁盘缓存(各2GB容量),以及通过自定义MyImgDecoder实现图片加载流程控制。该方案通过缓存机制和按需解码优化了图片加载性能,特别适合处理大图和频繁访问图片的场景。

XML 复制代码
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
Kotlin 复制代码
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.util.Log
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import kotlin.math.min


object ImgCache {
    private val TAG = "fly/ImgCache"

    fun readBmpDiskCache(item: MyMedia?): Bitmap? {
        var bitmap: Bitmap? = null

        val snapShot = MyCoilMgr.INSTANCE.loader().diskCache?.openSnapshot(item.toString())
        if (snapShot != null) {
            Log.d(TAG, "命中Disk缓存 $item")

            val source = ImageDecoder.createSource(snapShot.data.toFile())
            try {
                bitmap = ImageDecoder.decodeBitmap(source)
            } catch (e: Exception) {
                Log.e(TAG, "读Disk缓存异常 $e $item")
            }
        }

        snapShot?.close()

        return bitmap
    }

    fun writeBmpDiskCache(bitmap: Bitmap?, item: MyMedia?): Any? {
        var bool = false

        if (bitmap != null) {
            val editor = MyCoilMgr.INSTANCE.loader().diskCache?.openEditor(item.toString())

            var bos: BufferedOutputStream? = null
            try {
                bos = FileOutputStream(editor?.data?.toFile()).buffered(1024 * 32)
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
                bos.flush()
                bos.close()

                editor?.commit()
                Log.d(TAG, "Bitmap写入Disk缓存 $item")

                bool = true
            } catch (e: Exception) {
                Log.e(TAG, "Bitmap写Disk磁盘异常 $e")
            } finally {
                try {
                    bos?.close()
                } catch (e: Exception) {
                    Log.e(TAG, "$e $item")
                }
            }
        }

        return bool
    }


    fun decodeLargeImage(
        file: File,
        reqWidth: Int,
        reqHeight: Int
    ): Bitmap? {
        if (!file.exists() || reqWidth <= 0 || reqHeight <= 0) return null

        return try {
            val source = ImageDecoder.createSource(file)

            ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
                val srcW = info.size.width
                val srcH = info.size.height

                val scale = min(
                    reqWidth.toFloat() / srcW,
                    reqHeight.toFloat() / srcH
                ).coerceAtMost(1f)

                val finalW = (srcW * scale).toInt().coerceAtLeast(1)
                val finalH = (srcH * scale).toInt().coerceAtLeast(1)

                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
                decoder.memorySizePolicy = ImageDecoder.MEMORY_POLICY_LOW_RAM

                decoder.setTargetSize(finalW, finalH)
            }
        } catch (t: Throwable) {
            Log.e(TAG, "decodeLargeImage $t ${file.absolutePath} $reqWidth $reqHeight")
            null
        }
    }
}
Kotlin 复制代码
import android.os.Bundle
import android.util.Log
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.size.Scale
import coil3.toBitmap

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "fly/MainActivity"
        const val SIZE = 1000
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val imageView = findViewById<ImageView>(R.id.image)

        val filePath = "/sdcard/Download/data/xxx.jpg"

        val req = ImageRequest.Builder(this)
            .data(filePath)
            .size(SIZE)
            .scale(Scale.FIT)
            .decoderFactory(MyImgDecoder.Factory(MyMedia(filePath, SIZE, SIZE)))
            .listener(object : ImageRequest.Listener {
                override fun onSuccess(request: ImageRequest, result: SuccessResult) {
                    Log.d(TAG, "onSuccess ${request.data}")
                    imageView.setImageBitmap(result.image.toBitmap())
                }
            })
            .build()
        MyCoilMgr.INSTANCE.enqueue(req)
    }
}
Kotlin 复制代码
import android.app.Application

class MyApp : Application(){
    companion object {
        const val TAG = "fly/MyApp"
    }

    override fun onCreate() {
        super.onCreate()
        MyCoilMgr.INSTANCE.init(applicationContext)
    }
}
Kotlin 复制代码
import android.content.Context
import android.os.Environment
import android.util.Log
import coil3.EventListener
import coil3.ImageLoader
import coil3.decode.Decoder
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.fetch.Fetcher
import coil3.imageDecoderEnabled
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.Options
import java.io.File


class MyCoilMgr {
    companion object {

        const val TAG = "fly/MyCoilMgr"

        val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilMgr() }
    }

    private var mImageLoader: ImageLoader? = null

    private constructor() {
        Log.d(TAG, "constructor")
    }

    fun init(ctx: Context): ImageLoader {
        if (mImageLoader != null) {
            return mImageLoader!!
        }

        Log.d(TAG, "初始化ImageLoader")

        //初始化加载器。
        mImageLoader = ImageLoader.Builder(ctx)
            .imageDecoderEnabled(true)
            .memoryCachePolicy(CachePolicy.ENABLED)
            .memoryCache(initMemoryCache())
            .diskCachePolicy(CachePolicy.ENABLED)
            .diskCache(initDiskCache())
            .eventListener(object : EventListener() {
                override fun fetchStart(request: ImageRequest, fetcher: Fetcher, options: Options) {
                    Log.d(TAG, "fetchStart ${request.data}")
                }

                override fun decodeStart(request: ImageRequest, decoder: Decoder, options: Options) {
                    Log.d(TAG, "decodeStart ${request.data}")
                }
            })
            .components {

            }.build()

        return mImageLoader!!
    }

    private fun initMemoryCache(): MemoryCache {
        //内存缓存。
        val memoryCache = MemoryCache.Builder()
            .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
            .build()
        return memoryCache
    }

    private fun initDiskCache(): DiskCache {
        //磁盘缓存。
        val diskCacheFolder = Environment.getExternalStorageDirectory()
        val diskCacheName = "fly_disk_cache"

        val cacheFolder = File(diskCacheFolder, diskCacheName)
        if (cacheFolder.exists()) {
            Log.d(TAG, "${cacheFolder.absolutePath} exists")
        } else {
            if (cacheFolder.mkdir()) {
                Log.d(TAG, "${cacheFolder.absolutePath} create OK")
            } else {
                Log.e(TAG, "${cacheFolder.absolutePath} create fail")
            }
        }

        val diskCache = DiskCache.Builder()
            .maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
            .directory(cacheFolder)
            .build()

        Log.d(TAG, "cache folder = ${diskCache.directory.toFile().absolutePath}")

        return diskCache
    }


    fun enqueue(request: ImageRequest) {
        mImageLoader?.enqueue(request)
    }

    fun loader(): ImageLoader {
        return mImageLoader!!
    }
}
Kotlin 复制代码
import android.util.Log
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DecodeResult
import coil3.decode.Decoder
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import java.io.File

class MyImgDecoder(val item: MyMedia) : Decoder {
    companion object {
        const val TAG = "fly/MyImgDecoder"
    }

    override suspend fun decode(): DecodeResult? {
        val t = System.currentTimeMillis()

        var bimap = ImgCache.readBmpDiskCache(item)
        if (bimap == null) {
            Log.d(TAG, "没有磁盘缓存 $item")
            bimap = ImgCache.decodeLargeImage(File(item.filePath), MainActivity.SIZE, MainActivity.SIZE)
            Log.d(TAG, "decode() bimap ${bimap?.byteCount} $item")
            ImgCache.writeBmpDiskCache(bimap, item)
        } else {
            Log.d(TAG, "命中磁盘缓存  $item")
        }

        Log.d(TAG, "time cost =  ${System.currentTimeMillis() - t}ms $item")

        return DecodeResult(image = bimap?.asImage()!!, isSampled = false)
    }

    class Factory(val media: MyMedia) : Decoder.Factory {
        override fun create(
            result: SourceFetchResult,
            options: Options,
            imageLoader: ImageLoader,
        ): Decoder {
            return MyImgDecoder(item = media)
        }
    }
}
Kotlin 复制代码
class MyMedia {
    companion object {
        const val TAG = "fly/MyMedia"
    }

    var filePath: String? = null
    var width: Int = 0
    var height: Int = 0

    constructor(filePath: String, width: Int, height: Int) {
        this.filePath = filePath
        this.width = width
        this.height = height
    }

    override fun toString(): String {
        return "MyMedia(filePath=$filePath, width=$width, height=$height)"
    }
}

相关:

https://blog.csdn.net/zhangphil/article/details/150224812

相关推荐
鹏晨互联3 分钟前
【Compose vs XML:边框内外间距的实现对比】
android·xml
Android系统攻城狮9 分钟前
Android tinyalsa深度解析之pcm_plugin_write调用流程与实战(一百七十九)
android·pcm·tinyalsa·android16·音频进阶·android音频进阶
ID_1800790547319 分钟前
除了JSON,淘宝店铺商品API接口还支持哪些数据格式?
android·数据库
KillerNoBlood44 分钟前
2026移动端跨平台开发面经总结
android·算法·flutter·ios·移动开发·鸿蒙·kmp
消失的旧时光-19431 小时前
Android / IoT 面试复盘总结:从 MQTT、TLS 到 JWT 权限体系(标准答案 + 工程理解 + 延伸知识链)
android·物联网·面试
高林雨露2 小时前
Kotlin 的延迟初始化委托属性 by lazy
kotlin
林多2 小时前
【Android】 GPU过度绘制实现原理
android·gpu·性能·实现原理·过度绘制·overdraw
薄荷椰果抹茶2 小时前
手机端Obsidian安装与同步全攻略
android
醇氧2 小时前
CentOS 7安装 mysql-8.0.27-1.el7.x86_64.rpm 安装包
android·mysql·centos
号码认证服务2 小时前
给用户打电话,怎么在对方手机显示为“XX证券”?号码认证办理步骤
android·运维·服务器·ios·智能手机·iphone·webview