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博客 的基础上改进,主要是当正图加载出来后,主动删除相应的缓存中存放的缩略图,节省内存。
<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" />
implementation("io.coil-kt.coil3:coil:3.2.0")implementation("io.coil-kt.coil3:coil-gif:3.2.0")implementation("io.coil-kt.coil3:coil-core:3.2.0")implementation("io.coil-kt.coil3:coil-video:3.2.0")implementation("io.coil-kt.coil3:coil-svg:3.2.0")
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.launchclass MainActivity : AppCompatActivity() {companion object {const val THUMB_SIZE = 20const val IMAGE_SIZE = 350const val IMAGE = 1const val VIDEO = 2const val ROW_SIZE = 4const 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.VERTICALrv.layoutManager = layoutManagerval imageLoader = MyCoilManager.INSTANCE.getImageLoader(applicationContext)val adapter = MyAdapter(this, imageLoader)rv.adapter = adapterrv.layoutManager = layoutManagerrv.setItemViewCacheSize(ROW_SIZE * 10)rv.recycledViewPool.setMaxRecycledViews(0, ROW_SIZE * 10)val ctx = thislifecycleScope.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.sizeLog.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.65fval from = 50lists.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_IMAGEval 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>()//读取视频Videoval 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}
}
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.ImageLoaderclass MyAdapter : RecyclerView.Adapter<MyAdapter.ImageHolder> {private var mCtx: Context? = nullprivate var mImageLoader: ImageLoader? = nullprivate var mItems = ArrayList<MyData>()private var mScreenWidth = 0private var mPlaceHolderBmp: Bitmap? = nullprivate var mThumbError: Bitmap? = nullprivate var mImageError: Bitmap? = nullcompanion object {const val TAG = "fly/ImageAdapter"}constructor(ctx: Context, il: ImageLoader?) : super() {mCtx = ctxmScreenWidth = mCtx?.resources?.displayMetrics?.widthPixels!!mImageLoader = ilmPlaceHolderBmp = 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 = itemsnotifyDataSetChanged()}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}
}
import android.app.Application
import android.util.Log
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoaderclass 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)}
}
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.Fileclass MyCoilManager {companion object {const val TAG = "fly/MyCoilManager"val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }}private var mImageLoader: ImageLoader? = nullprivate var memoryCacheMaxSize = 0L// BitmapFactoryDecoder// internal const val DEFAULT_MAX_PARALLELISM = 4private val MAX_PARALLELISM = 4fun getImageLoader(ctx: Context): ImageLoader {if (mImageLoader != null) {Log.w(TAG, "ImageLoader已经初始化")return mImageLoader!!}Log.d(TAG, "初始化ImageLoader")//初始化加载器。mImageLoader = ImageLoader.Builder(ctx).imageDecoderEnabled(true) // false 对于一些特殊图,可以正常解码。.memoryCachePolicy(CachePolicy.ENABLED).memoryCache(initMemoryCache()).diskCachePolicy(CachePolicy.ENABLED).diskCache(initDiskCache()).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.maxSizereturn 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?.sizereturn "${sz?.toFloat()!! / memoryCacheMaxSize.toFloat()},$sz/$memoryCacheMaxSize"}
}
import android.net.Uriopen class MyData {var uri: Uri? = nullvar path: String? = nullvar lastModified = 0Lvar width = 0var height = 0var position = -1var type = -1 //-1未知。1,普通图。2,视频。constructor(uri: Uri?, path: String?, type: Int = -1) {this.uri = urithis.path = paththis.type = type}override fun toString(): String {return "MyData(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"}
}
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
import kotlin.math.roundToIntclass MyImgView : AppCompatImageView {companion object {const val TAG = "fly/MyImgView"}private var mCtx: Context? = nullprivate var mImageLoader: ImageLoader? = nullprivate var mScreenWidth: Int = 0private var mHeight: Int = 0private var mThumbDisposable: Disposable? = nullprivate var mImageDisposable: Disposable? = nullprivate var mPlaceHolderBmp: Bitmap? = nullprivate var mThumbError: Bitmap? = nullprivate var mImageError: Bitmap? = nullconstructor(ctx: Context,il: ImageLoader?,screenWidth: Int,placeHolderBmp: Bitmap?,thumbError: Bitmap?,imageError: Bitmap?) : super(ctx) {mCtx = ctxmImageLoader = ilmScreenWidth = screenWidthmHeight = (mScreenWidth.toFloat() / MainActivity.ROW_SIZE).roundToInt()scaleType = ScaleType.CENTER_CROPmPlaceHolderBmp = placeHolderBmpmThumbError = thumbErrormImageError = imageError}fun setData(mData: MyData) {clear()val imageItem = Item(mData)imageItem.memory = Item.MEM_IMAGEval 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 {var highQuality = falseval thumbItem = Item(mData)thumbItem.memory = Item.MEM_THUMBval thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())val thumbMemoryCache = MyCoilManager.INSTANCE.getMemoryCache(thumbMemoryCacheKey)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_SIZE, MainActivity.THUMB_SIZE).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(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 = truemThumbDisposable?.dispose()Log.d(TAG, "正图 onSuccess $imageItem 缓存状态=${MyCoilManager.INSTANCE.calMemoryCache()}")setImageBitmap(result.image.toBitmap())// 因为正图已经获取,缩略图不必存在// 清除缩略图缓存,节省内存。MyCoilManager.INSTANCE.memoryCache()?.remove(thumbMemoryCacheKey)}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 = 0const val MEM_IMAGE = 1}var memory = -1override fun toString(): String {return "Item(data=$data, memory=$memory)"}}
}
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? = nullval t = System.currentTimeMillis()try {bmp = ctx.contentResolver.loadThumbnail(item.data.uri!!, Size(MainActivity.THUMB_SIZE, MainActivity.THUMB_SIZE), 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)}}
}
Android Coli 3 ImageView load two suit Bitmap thumb and formal,Kotlin(六)-CSDN博客文章浏览阅读320次,点赞4次,收藏15次。本文介绍了在Android应用中使用Coil 3.2.0版本加载缩略图和正式图的实现方法,并提供了相关的Kotlin代码示例。文章提到,尽管配置了磁盘缓存路径,但实际运行时缓存文件为空,表明磁盘缓存未生效。此外,作者建议将缩略图和正图的内存缓存合并为单一缓存系统,以优化性能。文章还列出了所需的权限声明和Coil库的依赖项,包括对GIF、视频和SVG格式的支持。更多细节可参考CSDN博客链接。https://blog.csdn.net/zhangphil/article/details/147963941