Android Coil 3 ImageLoader MemoryCache根据Key复用内存缓存,Kotlin
<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" />
implementation("io.coil-kt.coil3:coil:3.1.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0")
或者:
implementation("io.coil-kt.coil3:coil-core:3.1.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0")
如果app过大,使用的loader需要自己管理和维护。
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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "fly"
const val SPAN_COUNT = 8
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rv = findViewById<RecyclerView>(R.id.rv)
val layoutManager = GridLayoutManager(this, SPAN_COUNT)
layoutManager.orientation = LinearLayoutManager.VERTICAL
val adapter = ImageAdapter(this, 0)
rv.adapter = adapter
rv.layoutManager = layoutManager
val ctx = this
lifecycleScope.launch(Dispatchers.IO) {
val lists = readAllImage(ctx)
Log.d(TAG, "readAllImage size=${lists.size}")
lifecycleScope.launch(Dispatchers.Main) {
adapter.dataChanged(lists)
}
}
}
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
}
}
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Environment
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.bitmapConfig
import coil3.request.placeholder
import coil3.request.target
import coil3.toBitmap
import okio.Path.Companion.toPath
import java.io.File
class ImageAdapter : RecyclerView.Adapter<ImageHolder> {
private var mCtx: Context? = null
private var mImageLoader: ImageLoader? = null
private var mViewSize = 0
private var mPlaceholder: Drawable? = null
constructor(ctx: Context, type: Int) : super() {
mCtx = ctx
//内存缓存。
val memoryCache = MemoryCache.Builder()
.maxSizeBytes(1024 * 1024 * 1024 * 1L) //1GB
.build()
//磁盘缓存。
val diskCacheFolder = Environment.getExternalStorageDirectory()
val diskCacheName = "coil_disk_cache"
val cacheFolder = File(diskCacheFolder, diskCacheName)
if (cacheFolder.exists()) {
Log.d(MainActivity.TAG, "${cacheFolder.absolutePath} exists")
} else {
if (cacheFolder.mkdir()) {
Log.d(MainActivity.TAG, "${cacheFolder.absolutePath} create OK")
} else {
Log.e(MainActivity.TAG, "${cacheFolder.absolutePath} create fail")
}
}
val diskCache = DiskCache.Builder()
.maxSizeBytes(1024 * 1024 * 1024 * 2L) //2GB
.directory(cacheFolder.absolutePath.toPath())
.build()
Log.d(MainActivity.TAG, "cache folder = ${diskCache.directory.toFile().absolutePath}")
//初始化加载器。
mImageLoader = ImageLoader.Builder(mCtx!!)
.memoryCachePolicy(CachePolicy.ENABLED)
.memoryCache(memoryCache)
.diskCachePolicy(CachePolicy.ENABLED)
.diskCache(diskCache)
.bitmapConfig(Bitmap.Config.ARGB_8888)
.build()
Log.d(MainActivity.TAG, "memoryCache.maxSize=${mImageLoader?.memoryCache?.maxSize}")
mViewSize = mCtx!!.resources.displayMetrics.widthPixels / MainActivity.SPAN_COUNT
mPlaceholder = ContextCompat.getDrawable(mCtx!!, R.mipmap.ic_launcher)
}
private var mItems = ArrayList<MainActivity.MyData>()
fun dataChanged(items: ArrayList<MainActivity.MyData>) {
this.mItems = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
val view = MyIV(mCtx!!, mViewSize)
return ImageHolder(view)
}
override fun getItemCount(): Int {
return mItems.size
}
override fun onBindViewHolder(holder: ImageHolder, position: Int) {
bind(mItems[position], holder.image)
}
private fun getMemoryCacheKey(data: MainActivity.MyData): MemoryCache.Key {
val extras = mutableMapOf<String, String>()
extras["path"] = data.path
extras["uri"] = data.uri.toString()
return MemoryCache.Key(data.path, extras)
}
private fun bind(data: MainActivity.MyData, myIv: MyIV) {
val bmp = mImageLoader?.memoryCache?.get(getMemoryCacheKey(data))?.image?.toBitmap(mViewSize, mViewSize, Bitmap.Config.ARGB_8888)
if (bmp != null && bmp.byteCount > 0) {
Log.d(
MainActivity.TAG,
"had memory cache bmp=${bmp.byteCount} ${mImageLoader?.memoryCache?.size}/${mImageLoader?.memoryCache?.maxSize}"
)
myIv.setImageBitmap(bmp)
} else {
Log.d(MainActivity.TAG, "no memory cache")
val memoryCacheKey = getMemoryCacheKey(data)
val request = ImageRequest.Builder(mCtx!!)
.data(data.uri)
.memoryCacheKey(memoryCacheKey)
.diskCacheKey(memoryCacheKey.toString())
.size(mViewSize)
.placeholder(mPlaceholder)
.target(myIv)
.build()
mImageLoader?.enqueue(request)
}
}
}
class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image = itemView as MyIV
}
class MyIV : AppCompatImageView {
private var mSize = 0
constructor(ctx: Context, size: Int) : super(ctx) {
mSize = size
scaleType = ScaleType.CENTER_CROP
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mSize, mSize)
}
}
遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。