Android Coil3配置Application单例ImageLoader,Kotlin
Android Coil3配置Application单例ImageLoader,Kotlin
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-network-okhttp:3.1.0")
<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" />
import android.app.Application
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 {
return MyCoilManager.INSTANCE().getImageLoader(this)
}
}
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import android.util.Log
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.gif.AnimatedImageDecoder
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.bitmapConfig
import okio.Path.Companion.toPath
import java.io.File
class MyCoilManager {
companion object {
const val TAG = "fly/MyCoilManager"
private val single by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyCoilManager() }
fun INSTANCE() = single
}
private var mImageLoader: ImageLoader? = null
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())
.networkCachePolicy(CachePolicy.ENABLED)
.bitmapConfig(Bitmap.Config.ARGB_8888)
.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()
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.absolutePath.toPath())
.build()
Log.d(TAG, "cache folder = ${diskCache.directory.toFile().absolutePath}")
return diskCache
}
}
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 coil3.imageLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
companion object {
const val SPAN_COUNT = 8
const val THUMB_WIDTH = 20
const val THUMB_HEIGHT = 20
const val IMAGE_SIZE = 400
}
private 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, SPAN_COUNT)
layoutManager.orientation = LinearLayoutManager.VERTICAL
val adapter = ImageAdapter(this, application.imageLoader)
rv.adapter = adapter
rv.layoutManager = layoutManager
rv.setItemViewCacheSize(SPAN_COUNT * 2)
rv.recycledViewPool.setMaxRecycledViews(0, SPAN_COUNT * 2)
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(imgList)
lists.addAll(videoList)
lists.shuffle()
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
}
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
}
}
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatImageView
import androidx.recyclerview.widget.RecyclerView
import coil3.Image
import coil3.ImageLoader
import coil3.asImage
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.target
import coil3.toBitmap
class ImageAdapter : RecyclerView.Adapter<ImageHolder> {
private var mCtx: Context? = null
private var mImageLoader: ImageLoader? = null
private var mViewSize = 0
private var mPlaceholderImage: Image? = null
private var mErrorBmp: Bitmap? = null
private val TAG = "fly/ImageAdapter"
constructor(ctx: Context, il: ImageLoader?) : super() {
mCtx = ctx
mImageLoader = il
mViewSize = mCtx!!.resources.displayMetrics.widthPixels / MainActivity.SPAN_COUNT
mPlaceholderImage = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.ic_menu_gallery).asImage()
mErrorBmp = BitmapFactory.decodeResource(mCtx!!.resources, android.R.drawable.stat_notify_error)
}
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) {
val data = mItems[position]
loadItemWithThumbForImage(data, holder.image)
}
private fun loadItemWithThumbForImage(data: MainActivity.MyData, myIv: MyIV) {
val thumbItem = Item(uri = data.uri, path = data.path)
thumbItem.type = Item.THUMB
val thumbMemoryCacheKey = MemoryCache.Key(thumbItem.toString())
val thumbMemoryCache = getMemoryCache(thumbMemoryCacheKey)
val imageItem = Item(uri = data.uri, path = data.path)
imageItem.type = Item.IMG
val imageMemoryCacheKey = MemoryCache.Key(imageItem.toString())
val imageMemoryCache = getMemoryCache(imageMemoryCacheKey)
var isHighQuality = false
var thumbDisposable: Disposable? = null
if (thumbMemoryCache == null && imageMemoryCache == null) {
val thumbReq = ImageRequest.Builder(mCtx!!)
.data(thumbItem)
.memoryCacheKey(thumbMemoryCacheKey)
.listener(object : ImageRequest.Listener {
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
if (!isHighQuality) {
myIv.setImageBitmap(result.image.toBitmap())
}
}
}).build()
thumbDisposable = mImageLoader?.enqueue(thumbReq)
}
var imgPlaceholder = mPlaceholderImage
if (thumbMemoryCache != null) {
imgPlaceholder = thumbMemoryCache.image
}
val imageReq = ImageRequest.Builder(mCtx!!)
.data(data.uri)
.memoryCacheKey(imageMemoryCacheKey)
.size(MainActivity.IMAGE_SIZE)
.target(myIv)
.placeholder(imgPlaceholder)
.listener(object : ImageRequest.Listener {
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
isHighQuality = true
thumbDisposable?.dispose()
Log.d(TAG, "image onSuccess ${result.dataSource} $imageItem ${calMemoryCache()}")
}
}).build()
mImageLoader?.enqueue(imageReq)
}
private fun getMemoryCache(key: MemoryCache.Key): MemoryCache.Value? {
return mImageLoader?.memoryCache?.get(key)
}
private fun calMemoryCache(): String {
return "${mImageLoader?.memoryCache?.size} / ${mImageLoader?.memoryCache?.maxSize}"
}
}
class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image = itemView as MyIV
}
class MyIV : AppCompatImageView {
companion object {
const val TAG = "fly/MyIV"
}
private var mSize = 0
private var mCtx: Context? = null
constructor(ctx: Context, size: Int) : super(ctx) {
mCtx = ctx
mSize = size
scaleType = ScaleType.CENTER_CROP
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(mSize, mSize)
}
}
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")
} 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)
}
}
}
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)"
}
}
Android Coil3缩略图、默认占位图placeholder、error加载错误显示,Kotlin(4)-CSDN博客文章浏览阅读469次,点赞9次,收藏6次。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。遗留问题,配置的disk cache似乎没有work,指定的磁盘缓存文件路径生成是生成了,但是app跑起来运行后(图正常显示),里面是空的。2、现在分别使用缩略图内存缓存和正图内存缓存,感觉应该可以合并,只使用一套内存缓存。https://blog.csdn.net/zhangphil/article/details/145832225Kotlin单例模式的一种懒汉模式写法_kotlin by lazy 实现单例模式-CSDN博客文章浏览阅读997次。kotlin中很容易使用object实现java中的单例模式。kotlin用object实现单例模式,companion object与java静态。kotlin用object实现单例模式,companion object与java静态_zhangphil的博客-CSDN博客。kotlin用object实现单例模式,companion object与java静态。kotlin用object实现单例模式,companion object与java静态_zhangphil的博客-CSDN博客。_kotlin by lazy 实现单例模式
https://blog.csdn.net/zhangphil/article/details/131555421