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

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

Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(三)-CSDN博客 进行完善,注意完善 MyImgView ,集中围绕正图的缓存是否存在展开加载逻辑。

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 复制代码
 <activity
           
            android:colorMode="wideColorGamut"
           >
          
        </activity>
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")
    implementation("io.coil-kt.coil3:coil-video:3.1.0")
    implementation("io.coil-kt.coil3:coil-svg:3.1.0")
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.CachePolicy
import coil3.request.ImageRequest
import com.appdemo.MyImgView.Item
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch


class MainActivity : AppCompatActivity() {
    companion object {
        const val THUMB_WIDTH = 100
        const val THUMB_HEIGHT = 100
        const val IMAGE_SIZE = 300

        const val IMAGE = 1
        const val VIDEO = 2

        const val ROW_SIZE = 8

        const val TAG = "fly/MainActivity"

        const val PRELOAD = false
    }

    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)
            }

            if (PRELOAD) {
                fetchLists(imageLoader, videoList)
            }
        }
    }

    private suspend fun fetchLists(imageLoader: ImageLoader, lists: ArrayList<MyData>) {
        val probability = 0.65f
        val from = 50

        lists.forEachIndexed { idx, myData ->
            if (idx > from && (Math.random() <= probability)) {
                Log.d(TAG, "$idx/${lists.size} preload")
                preload(imageLoader, myData)

                delay(10)
            }
        }
    }

    private suspend fun preload(imageLoader: ImageLoader, myData: MyData) {
        val imageItem = Item(myData)
        imageItem.memory = Item.MEM_IMAGE
        val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
        val imageMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(imageMemoryCacheKey)

        if (imageMemoryCache == null) {
            val imageReq = ImageRequest.Builder(this)
                .data(imageItem.data.path)
                .memoryCacheKey(imageMemoryCacheKey)
                .memoryCachePolicy(CachePolicy.WRITE_ONLY)
                .size(IMAGE_SIZE)
                .build()

            imageLoader.execute(imageReq)
        }
    }

    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(imageUri, path, IMAGE))
        }
        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(videoUri, path, VIDEO))
        }
        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

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.EventListener
import coil3.ImageLoader
import coil3.bitmapFactoryMaxParallelism
import coil3.decode.Decoder
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.gif.AnimatedImageDecoder
import coil3.imageDecoderEnabled
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.Options
import coil3.svg.SvgDecoder
import coil3.video.VideoFrameDecoder
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

    private val MAX_PARALLELISM = 6

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

        Log.d(TAG, "初始化ImageLoader")
        //初始化加载器。
        mImageLoader = ImageLoader.Builder(ctx)
            .imageDecoderEnabled(false) //false 对于一些特殊图,可以正常解码。
            .memoryCachePolicy(CachePolicy.ENABLED)
            .memoryCache(initMemoryCache())
            .diskCachePolicy(CachePolicy.ENABLED)
            .diskCache(initDiskCache())
            // BitmapFactoryDecoder
            // internal const val DEFAULT_MAX_PARALLELISM = 4
            .bitmapFactoryMaxParallelism(MAX_PARALLELISM)
            .eventListener(object : EventListener() {
                override fun decodeStart(request: ImageRequest, decoder: Decoder, options: Options) {
                    //Log.d(TAG, "decodeStart ${request.data}")
                }
            })
            .components {
                add(MyThumbFetcher.Factory(ctx))

                add(AnimatedImageDecoder.Factory())
                add(VideoFrameDecoder.Factory())
                add(SvgDecoder.Factory())
            }.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.net.Uri

open class MyData {
    var uri: Uri? = null
    var path: String? = null
    var lastModified = 0L
    var width = 0
    var height = 0

    var position = -1
    var type = -1  //-1未知。1,普通图。2,视频。

    constructor(uri: Uri?, path: String?, type: Int = -1) {
        this.uri = uri
        this.path = path
        this.type = type
    }

    override fun toString(): String {
        return "MyData(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"
    }
}
Kotlin 复制代码
import android.content.Context
import android.util.Log
import androidx.appcompat.widget.AppCompatImageView
import coil3.Bitmap
import coil3.ImageLoader
import coil3.asImage
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.request.bitmapConfig
import coil3.toBitmap


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(mData: MyData) {
        clear()

        val imageItem = Item(mData)
        imageItem.memory = Item.MEM_IMAGE
        val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
        val imageMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(imageMemoryCacheKey)

        if (imageMemoryCache != null) {
            Log.d(TAG, "命中正图缓存 $imageItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")
            setImageBitmap(imageMemoryCache.image.toBitmap())
        } else {
            val thumbItem = Item(mData)
            thumbItem.memory = Item.MEM_THUMB
            val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
            val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)

            var highQuality = false

            if (thumbMemoryCache != null) {
                Log.d(TAG, "命中缩略图缓存 $thumbItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")
                setImageBitmap(thumbMemoryCache.image.toBitmap())
            } else {
                setImageBitmap(mPlaceHolderBmp)

                val thumbReq = ImageRequest.Builder(mCtx!!)
                    .data(thumbItem)
                    .bitmapConfig(android.graphics.Bitmap.Config.RGB_565)
                    .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) {
                                setImageBitmap(result.image.toBitmap())
                            }
                        }

                        override fun onError(request: ImageRequest, result: ErrorResult) {
                            Log.e(TAG, "缩略图 onError $thumbItem")
                            if (!highQuality) {
                                setImageBitmap(mThumbError)
                                MyCoilManager.INSTANCE.memoryCache()?.set(thumbMemoryCacheKey, MemoryCache.Value(mThumbError?.asImage()!!))
                            }
                        }
                    }).build()
                mThumbDisposable = mImageLoader?.enqueue(thumbReq)
            }

            val imageReq = ImageRequest.Builder(mCtx!!)
                .data(
                    if (imageItem.data.type == MainActivity.VIDEO) {
                        imageItem.data.path
                    } else {
                        imageItem.data.uri
                    }
                )
                .bitmapConfig(android.graphics.Bitmap.Config.ARGB_8888)
                .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)

                        //如果正式的缩略图请求失败,后续放弃重复加载(无意义的重复加载),直接写入失败的异常占位图。
                        MyCoilManager.INSTANCE.memoryCache()?.set(imageMemoryCacheKey, MemoryCache.Value(mImageError?.asImage()!!))
                    }
                }).build()
            mImageDisposable = mImageLoader?.enqueue(imageReq)
        }
    }

    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)
    }

    class Item(val data: MyData) {
        companion object {
            //内存中的标记
            const val MEM_THUMB = 0
            const val MEM_IMAGE = 1
        }

        var memory = -1

        override fun toString(): String {
            return "Item(data=$data, memory=$memory)"
        }
    }
}
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 MyThumbFetcher(private val ctx: Context, private val item: MyImgView.Item, private val options: Options) : Fetcher {
    companion object {
        const val TAG = "fly/MyThumbFetcher"
    }

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

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

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

遗留问题,当有大量的视频且是异常视频时候,累积到某个视频解码任务多时候,后面的加载会被阻塞,疑似是Coil 3的总任务数量限制。

Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(三)-CSDN博客文章浏览阅读63次。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。2、现在分别使用缩略图内存缓存和正图内存缓存,感觉应该可以合并,只使用一套内存缓存。https://zhangphil.blog.csdn.net/article/details/146981186

相关推荐
每次的天空3 分钟前
Kotlin 内联函数深度解析:从源码到实践优化
android·开发语言·kotlin
练习本14 分钟前
Android MVC架构的现代化改造:构建清晰单向数据流
android·架构·mvc
早上好啊! 树哥36 分钟前
android studio开发:设置屏幕朝向为竖屏,强制应用的包体始终以竖屏(纵向)展示
android·ide·android studio
居然是阿宋1 小时前
C语言的中断 vs Java/Kotlin的异常:底层机制与高级抽象的对比
java·c语言·kotlin
YY_pdd1 小时前
使用go开发安卓程序
android·golang
Android 小码峰啊3 小时前
Android Compose 框架物理动画之捕捉动画深入剖析(29)
android·spring
bubiyoushang8883 小时前
深入探索Laravel框架中的Blade模板引擎
android·android studio·laravel
cyy2983 小时前
android 记录应用内存
android·linux·运维
CYRUS STUDIO4 小时前
adb 实用命令汇总
android·adb·命令模式·工具
大耳猫4 小时前
卡尔曼滤波算法简介与 Kotlin 实现
算法·kotlin·卡尔曼滤波