.
Coil 是一个 Android 图片加载库,通过
Kotlin
协程的方式加载图片。Coil 名字的由来:取 Coroutine Image Loader 首字母得来。
.
- 更快:
Coil 在性能上有很多优化,包括内存缓存和磁盘缓存,把缩略图存保存在内存中,循环利用
bitmap
,自动暂停和取消图片网络请求等。
- 更轻量级:
Coil 只有
2000个方法
(前提是你的 APP 里面集成了OkHttp
和Coroutines
),Coil 和Picasso
的方法数差不多,相比Glide
和Fresco
要轻量很多。
- 更容易使用:
Coil 的
API
充分利用了Kotlin
语言的新特性,简化和减少了很多样板代码。
- 更流行:
Coil 首选
Kotlin
语言开发并且使用包含Coroutines
,OkHttp
,Okio
和AndroidX Lifecycles
在内最流行的开源库。
.
io.coil-kt:coil-base
: 基础组件,提供了基本的图片请求、图片解码、图片缓存、Bitmap 复用等功能
io.coil-kt:coil
: 默认组件,依赖于io.coil-kt:coil-base
,提供了 Coil 类的单例对象以及ImageView
相关的扩展函数
io.coil-kt:coil-gif
: 包含两个decoder
用于支持解码GIFs
io.coil-kt:coil-svg
: 包含一个decoder
用于支持解码 SVG
io.coil-kt:coil-video
: 包含两个fetchers
用于支持读取和解码 任何 Android 的支持的视频格式 的视频帧
.
.
.
在项目build.gradle
文件中添加依赖
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
//核心库
implementation("io.coil-kt:coil:1.2.2")
//选择添加
implementation("io.coil-kt:coil-gif:1.2.2")//支持GIF
implementation("io.coil-kt:coil-svg:1.2.2")//支持SVG
implementation("io.coil-kt:coil-video:1.2.2")//支持Video
}
.
.
//加载网络图片
imageView.load("https://www.example.com/image.jpg")
//加载本地资源图片
imageView.load(R.drawable.image)
//加载文件里的图片
imageView.load(File("/path/to/image.jpg"))
使用可选的 `lambda` 块来添加配置项
imageView.load("https://www.example.com/image.jpg") {
crossfade(true) //淡入淡出
crossfade(1000)//设置显示动画的时间
placeholder(R.drawable.image) //占位图
error(R.drawable.image2)//图片加载失败时显示的图
}
.
Coil加载Gif图片
注: 需要添加
implementation("io.coil-kt:coil-gif:1.2.2")
依赖
val gifImageLoader = ImageLoader(this) {
componentRegistry {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder())
} else {
add(GifDecoder())
}
}
}
image.load(url, gifImageLoader)
.
Coil加载SVG图
注: 需要添加
implementation("io.coil-kt:coil-svg:1.2.2")
依赖
val svgImageLoader = ImageLoader(this){
componentRegistry {
add(SvgDecoder(this@MainActivity))
}
}
image.load(R.drawable.image_svg, svgImageLoader)
.
Coil加载频帧
注: 需要添加
implementation("io.coil-kt:coil-video:1.2.2")
依赖
val imageLoader = ImageLoader.Builder(context)
.componentRegistry {
add(VideoFrameFileFetcher())
add(VideoFrameUriFetcher())
}
.build()
imageView.load(File("/path/to/video.mp4",imageLoader)) {
videoFrameMillis(1000)
}
.
.
作用: 负责处理图片缓存、数据获取、图像解码、请求管理、
Bitmap
缓存池、内存管理等工作
val imageLoader = ImageLoader.Builder(context)
.availableMemoryPercentage(0.25)
.crossfade(true)
.build()
image.load(url, imageLoader)
补充:
为了达到性能最优,建议只创建一个ImageLoader
并进行共享。主要原因是每个ImageLoader
都有自己的内存缓存和Bitmap
缓存池
.
.
作用: 为
ImageLoader
加载图片提供所有的必要信息
ImageLoader
有两种方法可以执行ImageRequest
:区别:
enqueue():
将ImageRequest要在后台线程上异步执行的队列排入队列。execute():
ImageRequest在当前协程中执行并返回一个ImageResult。
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")//图片地址
.crossfade(true)
.memoryCachePolicy(CachePolicy.ENABLED)//设置内存的缓存策略
.diskCachePolicy(CachePolicy.ENABLED)//设置磁盘的缓存策略
.networkCachePolicy(CachePolicy.ENABLED)//设置网络的缓存策略
.target { drawable -> //图片加载之后的处理
//处理逻辑
}
.build()
//创建请求后,将其传递给ImageLoader以使其入队并执行
val imageLoader = imageLoader.enqueue(request)
image.load(imageLoader)
.
.
作用: 可以调用
ImageLoader
的execute()
方法来获取目标的Drawable图片
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
.build()
val drawable = imageLoader.execute(request).drawable
.
.
.
1. 将图像预加载到内存中:
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
//可选的,但设置ViewSizeResolver将通过限制图片应该预加载到内存的大小节省内存
.size(ViewSizeResolver(imageView))
.build()
imageLoader.enqueue(request)
2. 将网络图像仅预加载到磁盘缓存中,请禁用请求的内存缓存:
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
.memoryCachePolicy(CachePolicy.DISABLED)
.build()
imageLoader.enqueue(request)
.
.
作用: 取消图片加载,是调用
load()
方法后的返回值
val disposable = imageView.load("https://www.example.com/image.jpg")
//取消正在进行的图片加载请求以及释放相关的资源
disposable.dispose()
//非阻塞式地等待任务结束
disposable.await()
.
.
Coil默认提供了四种变换:
BlurTransformation()
: 高斯模糊变换CircleCropTransformation()
: 圆形裁剪变换GrayscaleTransformation()
: 灰度变换RoundedCornersTransformation()
: 圆角变换
imageView.load("https://www.example.com/image.jpg") {
transformations(
CircleCropTransformation()//图片变换,将图片转为圆形
)
}
.
自定义图片变换
图片变换是基本所有的图片加载库都会支持的功能,Coil 对这个概念的抽象即
Transformation
接口,因此我们可以通过实现Transformation
接口来实现自定义图片变换
.
Transformation接口:
interface Transformation {
/**
* Return a unique key for this transformation.
*
* The key should contain any params that are part of this transformation (e.g. size, scale, color, radius, etc.).
*/
fun key(): String
/**
* Apply the transformation to [input].
*
* @param pool A [BitmapPool] which can be used to request [Bitmap] instances.
* @param input The input [Bitmap] to transform. Its config will always be [Bitmap.Config.ARGB_8888] or [Bitmap.Config.RGBA_F16].
* @param size The size of the image request.
*/
suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap
}
注意
key()
方法的返回值是用于计算图片在内存缓存中的唯一Key
时的辅助参数,所以需要实现该方法,为Transformation
生成一个可以唯一标识自身的字符串Key
。transform
方法包含了一个BitmapPool
参数,我们在实现图形变换的时候往往是需要一个全新的Bitmap
,此时就应该通过BitmapPool
来获取,尽量复用已有的Bitmap
.
1、为图片添加水印
为图片添加水印的思路也很简单,只需要对
canvas
稍微坐下旋转,然后绘制文本即可
class WatermarkTransformation(private val watermark: String, @ColorInt private val textColor: Int,private val textSize: Float) : Transformation {
override fun key(): String {
return "${WatermarkTransformation::class.java.name}-${watermark}-${textColor}-${textSize}"
}
override suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap {
val width = input.width
val height = input.height
val config = input.config
val output = pool.get(width, height, config)
val canvas = Canvas(output)
val paint = Paint()
paint.isAntiAlias = true
canvas.drawBitmap(input, 0f, 0f, paint)
canvas.rotate(40f, width / 2f, height / 2f)
paint.textSize = textSize
paint.color = textColor
val textWidth = paint.measureText(watermark)
canvas.drawText(watermark, (width - textWidth) / 2f, height / 2f, paint)
return output
}
}
//使用自定义的变换
imageView.load(imageUrl) {
transformations(
WatermarkTransformation("萝莉", Color.parseColor("#8D3700B3"), 120f)
)
}
.
2、为图片添加蒙层
Android 的
Paint
原生就支持为Bitmap
添加一个蒙层,只需要使用其colorFilter()
方法即可
class ColorFilterTransformation(@ColorInt private val color: Int) : Transformation {
override fun key(): String = "${ColorFilterTransformation::class.java.name}-$color"
override suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap {
val width = input.width
val height = input.height
val config = input.config
val output = pool.get(width, height, config)
val canvas = Canvas(output)
val paint = Paint()
paint.isAntiAlias = true
paint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
canvas.drawBitmap(input, 0f, 0f, paint)
return output
}
}
//使用自定义的变换
imageView.load(imageUrl) {
transformations(
ColorFilterTransformation(Color.parseColor("#9CF44336"))
)
}
更多 Transformation 效果看这里:coil-transformations库
.
.
说明: Coil 的缓存机制是分为内存缓存,磁盘缓存 与 网络缓存,默认情况下,这三层缓存机制是全部启用的,即:全部可读可写
在请求图片的时候,我们可以在 lambda 块中配置本次请求的缓存策略
缓存策略有如下几种:
CachePolicy.ENABLED
: 可读可写CachePolicy.READ_ONLY
: 只读CachePolicy.WRITE_ONLY
: 只写CachePolicy.DISABLED
: 不可读不可写,即禁用
imageView.load(imageUrl) {
memoryCachePolicy(CachePolicy.ENABLED)//设置内存的缓存策略
diskCachePolicy(CachePolicy.ENABLED)//设置磁盘的缓存策略
networkCachePolicy(CachePolicy.ENABLED)//设置网络的缓存策略
}
.
默认情况下,每个 ImageLoader
都已设置为磁盘缓存,并将根据用户设备上的剩余空间设置 10-250MB
之间的最大缓存大小。但是,如果您设置了自定义OkHttpClient
,则需要自己添加磁盘缓存,代码如下:
val imageLoader = ImageLoader.Builder(context)
.okHttpClient {
OkHttpClient.Builder()
.cache(CoilUtils.createDefaultCache(context))
.build()
}
.build()
imageView.load(imageUrl)
.
.
说明: 如果我们想要设置应用内所有图片在加载时固定显示同一张
loading
图,在加载失败时固定显示一张error
图, 那么就需要为 Coil 设定一个全局的默认配置。可以通过下面的方式来实现
.
方式一:
Coil.setImageLoader(
ImageLoader.Builder(application)
.placeholder(ActivityCompat.getDrawable(application, R.drawable.icon_loading)) //占位符
.error(ActivityCompat.getDrawable(application, R.drawable.icon_error)) //错误图
.memoryCachePolicy(CachePolicy.ENABLED) //开启内存缓存
.callFactory(createOkHttp(application)) //主动构造 OkHttpClient 实例
.build()
)
private fun createOkHttp(application: Application): OkHttpClient {
return OkHttpClient.Builder()
.cache(createDefaultCache(application))
.build()
}
private fun createDefaultCache(context: Context): Cache {
val cacheDirectory = getDefaultCacheDirectory(context)
return Cache(cacheDirectory, 10 * 1024 * 1024)
}
private fun getDefaultCacheDirectory(context: Context): File {
return File(context.cacheDir, "image_cache").apply { mkdirs() }
}
val url = "https://notfound.png"
val imageView = findViewById<ImageView>(R.id.image_view)
val reloadButton = findViewById<Button>(R.id.reload_button)
reloadButton.setOnClickListener {
imageView.load(url)
}
.
方式二:
class MyApplication : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(applicationContext)
.crossfade(true)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.availableMemoryPercentage(0.1)
.bitmapPoolPercentage(0.1)
.build()
}
}
.
全局的默认ImageLoader的使用
说明:
ImageLoader
可以使用Context.imageLoader
扩展函数检索单例
val imageLoader = context.imageLoader
.
.
请自行阅读文档这里就不做过多介绍了,因为用的机会不多。
.
.
1. 基本用法
// Glide
Glide.with(context)
.load(url)
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.into(imageView)
// Coil
imageView.load(url)
.
2. 通过设置图片ScaleType的方式:
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
// Glide
Glide.with(context)
.load(url)
.placeholder(placeholder)
.fitCenter()
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.placeholder(placeholder)
.fit()
.into(imageView)
// Coil (autodetects the scale type)
imageView.load(url) {
placeholder(placeholder)
}
.
.
Coil 开箱即用,与
R8
完全兼容,不需要添加任何额外规则如果你使用了
Proguard
,你可能需要添加对应的混淆规则:Coroutines
、OkHttp
andOkio
。
.
Coroutines的混淆规则
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
-keepclassmembers class kotlin.coroutines.SafeContinuation {
volatile <fields>;
}
# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
-dontwarn java.lang.instrument.Instrumentation
-dontwarn sun.misc.Signal
.
OkHttp的混淆规则
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform
-dontwarn org.conscrypt.ConscryptHostnameVerifier
.
Okio的混淆规则
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
.
.
Coil的GitHub地址
Coil的API文档
Coil的源码分析文章