Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin

Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin

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" />
XML 复制代码
    implementation("io.coil-kt.coil3:coil:3.1.0")
    implementation("io.coil-kt.coil3:coil-gif:3.1.0")
    implementation("io.coil-kt.coil3:coil-core:3.1.0")
Kotlin 复制代码
import android.net.Uri

class Item {
    companion object {
        const val THUMB = 0
        const val IMG = 1
    }

    var uri: Uri? = null
    var path: String? = null
    var lastModified = 0L
    var width = 0
    var height = 0

    var position = -1
    var type = -1  //0,缩略图。 1,正图image。-1,未知。

    constructor(uri: Uri, path: String) {
        this.uri = uri
        this.path = path
    }

    override fun toString(): String {
        return "Item(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"
    }
}
Kotlin 复制代码
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader
import coil3.memory.MemoryCache
import coil3.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


class MainActivity : AppCompatActivity() {
    companion object {
        const val THUMB_WIDTH = 30
        const val THUMB_HEIGHT = 30
        const val IMAGE_SIZE = 150

        const val ROW_SIZE = 8

        const val TAG = "fly/MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val rv = findViewById<RecyclerView>(R.id.rv)

        val layoutManager = GridLayoutManager(this, ROW_SIZE)
        layoutManager.orientation = GridLayoutManager.VERTICAL
        rv.layoutManager = layoutManager

        val imageLoader = MyCoilManager.INSTANCE.getImageLoader(applicationContext)
        val adapter = MyAdapter(this, imageLoader)

        rv.adapter = adapter
        rv.layoutManager = layoutManager

        rv.setItemViewCacheSize(ROW_SIZE * 10)
        rv.recycledViewPool.setMaxRecycledViews(0, ROW_SIZE * 10)

        val ctx = this
        lifecycleScope.launch(Dispatchers.IO) {
            val imgList = readAllImage(ctx)
            val videoList = readAllVideo(ctx)

            Log.d(TAG, "readAllImage size=${imgList.size}")
            Log.d(TAG, "readAllVideo size=${videoList.size}")

            val lists = arrayListOf<MyData>()
            lists.addAll(videoList)
            lists.addAll(imgList)

            val total = lists.size
            Log.d(TAG, "总数量=$total")
            lists.shuffle()

            lifecycleScope.launch(Dispatchers.Main) {
                adapter.dataChanged(lists)
            }

            val PRELOAD = false
            if (PRELOAD) {
                val probability = 0.85f
                val from = 30
                lists.forEachIndexed { idx, myData ->
                    if (idx > from && (Math.random() <= probability)) {
                        Log.d(TAG, "$idx/$total preload")
                        preload(imageLoader, myData)
                    }
                }
            }
        }
    }

    private fun preload(imageLoader: ImageLoader, myData: MyData) {
        val thumbItem = Item(uri = myData.uri, path = myData.path)
        thumbItem.type = Item.THUMB

        val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
        val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)

        if (thumbMemoryCache == null) {
            val thumbReq = ImageRequest.Builder(this)
                .data(thumbItem)
                .size(THUMB_WIDTH, THUMB_HEIGHT)
                .memoryCacheKey(thumbMemoryCacheKey)
                .build()
            imageLoader.enqueue(thumbReq)
        }
    }

    class MyData(var path: String, var uri: Uri)

    private fun readAllImage(ctx: Context): ArrayList<MyData> {
        val photos = ArrayList<MyData>()

        //读取所有图
        val cursor = ctx.contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null
        )

        while (cursor!!.moveToNext()) {
            //路径
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))

            val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
            val imageUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))

            //名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))

            //大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))

            photos.add(MyData(path, imageUri))
        }
        cursor.close()

        return photos
    }

    private fun readAllVideo(context: Context): ArrayList<MyData> {
        val videos = ArrayList<MyData>()

        //读取视频Video
        val cursor = context.contentResolver.query(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            null
        )

        while (cursor!!.moveToNext()) {
            //路径
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))

            val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
            val videoUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))

            //名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))

            //大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))

            videos.add(MyData(path, videoUri))
        }
        cursor.close()

        return videos
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.BitmapFactory
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil3.Bitmap
import coil3.ImageLoader
import com.appdemo.MainActivity.MyData

class MyAdapter : RecyclerView.Adapter<MyAdapter.ImageHolder> {
    private var mCtx: Context? = null
    private var mImageLoader: ImageLoader? = null
    private var mItems = ArrayList<MyData>()
    private var mScreenWidth = 0

    private var mPlaceHolderBmp: Bitmap? = null
    private var mThumbError: Bitmap? = null
    private var mImageError: Bitmap? = null

    companion object {
        const val TAG = "fly/ImageAdapter"
    }

    constructor(ctx: Context, il: ImageLoader?) : super() {
        mCtx = ctx
        mScreenWidth = mCtx?.resources?.displayMetrics?.widthPixels!!
        mImageLoader = il

        mPlaceHolderBmp = BitmapFactory.decodeResource(mCtx!!.resources, R.mipmap.loading)
        mThumbError = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.ic_menu_gallery)
        mImageError = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.stat_sys_warning)
    }

    fun dataChanged(items: ArrayList<MyData>) {
        this.mItems = items
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
        val view = MyImgView(mCtx!!, mImageLoader, mScreenWidth, mPlaceHolderBmp, mThumbError, mImageError)
        return ImageHolder(view)
    }

    override fun onBindViewHolder(holder: ImageHolder, position: Int) {
        holder.image.setData(mItems[position])
    }

    override fun getItemCount(): Int {
        return mItems.size
    }

    class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var image = itemView as MyImgView
    }
}
Kotlin 复制代码
import android.app.Application
import android.util.Log
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader


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

    override fun newImageLoader(context: PlatformContext): ImageLoader {
        Log.d(TAG, "newImageLoader")
        return MyCoilManager.INSTANCE.getImageLoader(this)
    }
}
Kotlin 复制代码
import android.content.Context
import android.os.Environment
import android.util.Log
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.gif.AnimatedImageDecoder
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import java.io.File


class MyCoilManager {
    companion object {
        const val TAG = "fly/MyCoilManager"

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

    private var mImageLoader: ImageLoader? = null
    private var memoryCacheMaxSize = 0L

    fun getImageLoader(ctx: Context): ImageLoader {
        if (mImageLoader != null) {
            Log.w(TAG, "ImageLoader已经初始化")
            return mImageLoader!!
        }

        Log.d(TAG, "初始化ImageLoader")
        //初始化加载器。
        mImageLoader = ImageLoader.Builder(ctx)
            .memoryCachePolicy(CachePolicy.ENABLED)
            .memoryCache(initMemoryCache())
            .diskCachePolicy(CachePolicy.ENABLED)
            .diskCache(initDiskCache())
            .components {
                add(AnimatedImageDecoder.Factory())
                add(ThumbFetcher.Factory(ctx))
            }.build()

        Log.d(TAG, "memoryCache.maxSize=${mImageLoader!!.memoryCache?.maxSize}")

        return mImageLoader!!
    }

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

        memoryCacheMaxSize = memoryCache.maxSize

        return memoryCache
    }

    private fun initDiskCache(): DiskCache {
        //磁盘缓存。
        val diskCacheFolder = Environment.getExternalStorageDirectory()
        val diskCacheName = "coil_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 getMemoryCache(key: MemoryCache.Key): MemoryCache.Value? {
        return mImageLoader?.memoryCache?.get(key)
    }

    fun memoryCache(): MemoryCache? {
        return mImageLoader?.memoryCache
    }

    fun calMemoryCache(): String {
        val sz = mImageLoader?.memoryCache?.size
        return "${sz?.toFloat()!! / memoryCacheMaxSize.toFloat()},$sz/$memoryCacheMaxSize"
    }
}
Kotlin 复制代码
import android.content.Context
import android.util.Log
import androidx.appcompat.widget.AppCompatImageView
import coil3.Bitmap
import coil3.ImageLoader
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.Disposable
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.toBitmap
import com.appdemo.MainActivity.MyData


class MyImgView : AppCompatImageView {
    companion object {
        const val TAG = "fly/MyImgView"

        //整数相除,精度损失的平衡因子
        const val BALANCE_FACTOR = 1
    }

    private var mCtx: Context? = null
    private var mImageLoader: ImageLoader? = null
    private var mScreenWidth: Int = 0
    private var mHeight: Int = 0

    private var mThumbDisposable: Disposable? = null
    private var mImageDisposable: Disposable? = null

    private var mPlaceHolderBmp: Bitmap? = null
    private var mThumbError: Bitmap? = null
    private var mImageError: Bitmap? = null

    constructor(
        ctx: Context,
        il: ImageLoader?,
        screenWidth: Int,
        placeHolderBmp: Bitmap?,
        thumbError: Bitmap?,
        imageError: Bitmap?
    ) : super(ctx) {
        mCtx = ctx
        mImageLoader = il

        mScreenWidth = screenWidth
        mHeight = mScreenWidth / MainActivity.ROW_SIZE + BALANCE_FACTOR

        scaleType = ScaleType.CENTER_CROP

        mPlaceHolderBmp = placeHolderBmp
        mThumbError = thumbError
        mImageError = imageError
    }

    fun setData(myData: MyData) {
        clear()

        val thumbItem = Item(uri = myData.uri, path = myData.path)
        thumbItem.type = Item.THUMB
        val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
        val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)

        val imageItem = Item(uri = myData.uri, path = myData.path)
        imageItem.type = Item.IMG
        val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
        val imageMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(imageMemoryCacheKey)

        if (thumbMemoryCache == null && imageMemoryCache == null) {
            setImageBitmap(mPlaceHolderBmp)
        }

        var highQuality = false

        if (thumbMemoryCache == null) {
            val thumbReq = ImageRequest.Builder(mCtx!!)
                .data(thumbItem)
                .memoryCacheKey(thumbMemoryCacheKey)
                .memoryCachePolicy(CachePolicy.WRITE_ONLY)
                .size(MainActivity.THUMB_WIDTH, MainActivity.THUMB_HEIGHT)
                .listener(object : ImageRequest.Listener {
                    override fun onSuccess(request: ImageRequest, result: SuccessResult) {
                        Log.d(TAG, "缩略图 onSuccess $thumbItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")

                        if (!highQuality) {
                            this@MyImgView.setImageBitmap(result.image.toBitmap())
                        }
                    }

                    override fun onError(request: ImageRequest, result: ErrorResult) {
                        Log.e(TAG, "缩略图 onError $thumbItem")

                        if (!highQuality) {
                            setImageBitmap(mThumbError)
                        }
                    }
                }).build()
            mThumbDisposable = mImageLoader?.enqueue(thumbReq)
        } else {
            Log.d(TAG, "命中缩略图缓存 $thumbItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")
            this@MyImgView.setImageBitmap(thumbMemoryCache.image.toBitmap())
        }

        if (imageMemoryCache == null) {
            val imageReq = ImageRequest.Builder(mCtx!!)
                .data(myData.uri)
                .memoryCacheKey(imageMemoryCacheKey)
                .memoryCachePolicy(CachePolicy.WRITE_ONLY)
                .size(MainActivity.IMAGE_SIZE).listener(object : ImageRequest.Listener {
                    override fun onSuccess(request: ImageRequest, result: SuccessResult) {
                        highQuality = true
                        mThumbDisposable?.dispose()

                        Log.d(
                            TAG, "正图 onSuccess $imageItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}"
                        )

                        setImageBitmap(result.image.toBitmap())
                    }

                    override fun onError(request: ImageRequest, result: ErrorResult) {
                        Log.e(TAG, "正图 onError $imageItem")
                        setImageBitmap(mImageError)
                    }
                }).build()
            mImageDisposable = mImageLoader?.enqueue(imageReq)
        } else {
            Log.d(TAG, "命中正图缓存 $imageItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")
            setImageBitmap(imageMemoryCache.image.toBitmap())
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()

        //强化clear
        //clear()
    }

    private fun clear() {
        Log.d(TAG, "clear")

        mThumbDisposable?.dispose()
        mImageDisposable?.dispose()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mHeight, mHeight)
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.util.Size
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DataSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.ImageFetchResult
import coil3.request.Options

/**
 * 例如 FileUriFetcher
 */
class ThumbFetcher(private val ctx: Context, private val thumbItem: Item, private val options: Options) : Fetcher {
    companion object {
        const val TAG = "fly/ThumbFetcher"
    }

    override suspend fun fetch(): FetchResult {
        var bmp: Bitmap? = null
        val t = System.currentTimeMillis()
        try {
            bmp = ctx.contentResolver.loadThumbnail(thumbItem.uri!!, Size(MainActivity.THUMB_WIDTH, MainActivity.THUMB_HEIGHT), null)
            Log.d(TAG, "loadThumbnail time cost=${System.currentTimeMillis() - t} $thumbItem ${MyCoilManager.INSTANCE.calMemoryCache()}")
        } catch (e: Exception) {
            Log.e(TAG, "e=$e ThumbItem=$thumbItem")
        }

        return ImageFetchResult(
            bmp?.asImage()!!,
            true,
            dataSource = DataSource.DISK
        )
    }

    class Factory(private val ctx: Context) : Fetcher.Factory<Item> {
        override fun create(
            data: Item,
            options: Options,
            imageLoader: ImageLoader,
        ): Fetcher {
            return ThumbFetcher(ctx, data, options)
        }
    }
}

遗留问题:对于一些异型、特殊视频,Coil3原生的解码有些情况。下一步打算单独做一个视频的解码module,像缩略图那种。

Android Coil3缩略图直接内存复用,Kotlin(6)_android kotlin coil-CSDN博客文章浏览阅读276次,点赞3次,收藏6次。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。2、现在分别使用缩略图内存缓存和正图内存缓存,感觉应该可以合并,只使用一套内存缓存。_android kotlin coilhttps://blog.csdn.net/zhangphil/article/details/146429297

相关推荐
好的佩奇9 小时前
蓝牙之通信架构
android·flutter·蓝牙
FlightYe9 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频
诸神黄昏EX10 小时前
Android fw BatteryService
android
alexhilton10 小时前
提升 Android Compose 页面的流畅度
android·kotlin·android jetpack
撩得Android一次心动12 小时前
Android中Gradle网络下载超时、更改存放位置(解决方案)
android·gradle
HQL_seven13 小时前
【Activity】Android Activity详解
android
AI2中文网13 小时前
安卓 15/16 手机软键盘遮挡输入框、布局顶不上去?已解决:重新编译即可
android·智能手机
字节暗面13 小时前
Android APP 加固横评记录:爱加密、梆梆、网易易盾与腾讯 ACE
android·逆向工程·加固
落魄Android在线炒饭14 小时前
Android 实战问题反推 OpenGL 概念(上)
android·opengl
booboosui14 小时前
DeepSeek 的 Worker 调得动 GitHub,调不动微信:三类 Worker 的实现差别
android