前言
Coil 作为图片加载库的新秀,和 Glide、Picasso 这些老牌图片库相比,它们的优缺点是什么以及 Coil 未来的展望?先来了解一下什么是 Coil。
Coil 是基于 Kotlin 开发的首个图片加载库,来自 Instacart 团队,来看看官网对它的最新的介绍。
- R8:Coil 下完全兼容 R8,所以不需要添加任何与 Coil 相关的混淆器规则。
- Fast:Coil 进行了许多优化,包括内存和磁盘缓存,对内存中的图片进行采样,重新使用位图,自动暂停/取消请求等等。
- Lightweight:Coil 为你的 APK 增加了 2000 个方法(对于已经使用了 OkHttp 和协程的应用程序),这与 Picasso 相当,明显少于 Glide 和 Fresco。
- Easy to use:Coil 利用了 Kotlin 的语言特性减少了样版代码。
- Modern:使用了大量的高级特性,例如协程、OkHttp、和 androidX lifecycle 跟踪生命周期状态的,Coil 是目前唯一支持 androidX lifecycle 的库。
通过这篇文章你将学习到以下内容,将在译者思考部分会给出相应的答案
- Kotlin 如何使用一行代码交换两个变量?
- Coil 和 Glide 和 Picasso 比较,它们优缺点是什么?
- Kotlin 作为 Android 开发的首选语言,我们该如何进行选择 Coil、Glide 和 Picasso?
- 如何在项目中使用 Coil?
- Coil、Glide 和 Picasso 使用上大比拼?
- Coil 的动态图片采样是什么?
接下来演示中表格中的数字,可能很难理解,但是为了更好地理解,在最后的部分,会以柱状图清晰的展示每个库的性能的对比,在译者思考部分会更深入的分析这三个图片加载库的性能,请耐心多读几遍,应该可以从中学到很多技巧。
译文
Coil 作为图片库的新秀,越来越受欢迎了,但是为什么会引起这么多人的关注?在当今主流的图片加载库环境中 Coil 是一股清流,它是轻量级的,因为它使用了许多 Android 开发者已经在他们的项目中包含的其他库(协程、Okhttp)。
当我第一次看到这个库时,我认为这些都是很好的改进,但是我很想知道和其他主流的图片加载库相比,这个库能带来哪些好处,这篇文章的主要目的是分析一下 Coil 的性能,接下来我们来对比一下 Coil、 Glide 和 Picasso。
我们如何测量
为了弄清楚这些库的性能如何,我们分别用它们实现了一个应用程序,这是一个简单的应用程序,它下载 10 张图片并以网格布局显示它们,如下图所示。
[图片上传失败...(image-5a9a19-1591858638011)]
<figcaption></figcaption>
所有图片都是在同一时间可见的,几乎是在同一时间被请求的,因此,可以认为它们是并行加载。
- 测试加载每张图片所需的时间:为了获得更准确的数据,图片被下载了十次将取平均值。
- 计算加载图片列表所需的时间:这个数字很重要,因为图片是并行加载的,所以不能从单个图片推断。这个测试也做了十次测试将取平均值。
另一个需要测试的重要点是第一次加载图片和从缓存中加载它们所花费的时间,已经进行了多次测试,覆盖了上面的场景。
从网络下载
我们开始第一个场景,当缓存为空时,从网络中下载图片。
Glide
在下面的表中,您可以看到当缓存为空时,从网络中下载图片所用的时间。注意,这些时间是测试 10 次之后的平均值。

下表展示了加载完整的图片列表所需的时间,以及平均时间。

Picasso
Picasso 的测试和 Glide 相同,当缓存为空时,从网络中下载图片,测试 10 次左右所用的时间的平均值。

以及加载完整的图片列表所需要的时间,以及平均时间。

Coil
现在来看一下今天主角 Coil ,和 Picasso、Glide 做相同的测试,当缓存为空时,从网络下载 10 次左右所用的时间的平均值。

以及加载完整的图片列表所需的时间,以及平均时间。

从缓存中加载
现在开始另外一个场景测试,当缓存不为空时,加载图片所需要的时间。
Glide
从缓存中加载图片所用时间,如下表所示。

从缓存中加载完整的图片列表所需的时间,以及平均时间。

Picasso
测试用例和 Glide 相同,从缓存中加载图片所用时间,如下表所示。

以及从缓存中加载完整的图片列表所需的时间,以及平均时间。

Coil
最后我们来看一下 Coil 如何呢,从缓存中加载图片所用时间,如下表所示。

同样的从缓存中加载完整的图片列表所需的时间,以及平均时间。

结论
为了更好地理解我们在测试中得到的结果,我们可以从下面图表中看到这些数字,反应了从网络下载每个图片时的结果。

Glide 最快 Picasso 和 Coil 几乎相同。
但是当我们从缓存中加载的时候,正如你在下面的图片中看到的,在大多数情况下,Glide 最快,Coil 其次,Picasso 最慢。

另一个重要的测试加载完整的图片列表所花费的时间,这些数字非常重要,因为这是用户等待看到整个图片列表的时间。当图片从网络加载时,Glide 是最快的,其次是Picasso,Coil是最慢的。
从缓存加载的结果是不同的。Glide 和 Coil 几乎相同,Picasso 是最慢的。

从这些数字中我们可以得出几个结论:
- 有许多场景需要测试,例如,下载大图片,调整图片大小以适应容器等等。因此,我不能说其他情况下的结果可能会有很大的不同。
- 正如您所看到的,统计数据是一门包含大量数据的科学,因此对每个场景进行 10 次测试不足以具体的说明那个库最好,但是我们可以粗略地了解性能。
- Glide 似乎在大多数情况下更快,但数量一般不是很大情况。如果你需要很好地执行,或者你正在下载很多图片,这可能对你来说是非常有用。此外,如果我们使用大图片,这些测试的结果可能会改变。
- Coil 作为图片加载库的新秀,未来它的性能可能会有很大提高,现在只是我们将它与成熟的图片加载库进行比较的结果。
译者思考
作者从以下场景对 Coil、Glide、Picasso 做了全面的测试。
-
当缓存为空时,从网络中下载图片的平均时间。
-
从网络中下载图片所用的时间。 结果:Glide 最快 Picasso 和 Coil 几乎相同。
-
加载完整的图片列表所用的时间,以及平均时间。 结果:Glide 是最快的,其次是Picasso,Coil是最慢的。
-
-
当缓存不为空时,从缓存中加载图片的平均时间。
-
从缓存中加载图片所用的时间。 结果:Glide 最快,Coil 其次,Picasso 最慢。
-
加载完整的图片列表所用的时间,以及平均时间。 结果:Glide 和 Coil 几乎相同,Picasso 是最慢的。
-
图片加载库的选择是我们应用程序中最重要的部分之一,根据以上结果,如果你的应用程序中没有大量使用图片的时候,我认为使用 Coil 更好,原因有以下几点:
- 与 Glide 和 Fresco 类似,Coil 支持位图池,位图池是一种重新使用不再使用的位图对象的技术,这可以显著提高内存性能(特别是在oreo之前的设备上),但是它会造成一些 API 限制。
- Coil 是基于 Kotlin 开发的,为 Kotlin 使用而设计的,所以代码通常更简洁更干净。
- Kotlin 作为 Android 首选语言,Coil 是为 Kotlin 而设计的,Coil 在未来肯定会大方光彩。
- 从 Glide、Picasso 迁移到 Coil 是非常的容易,API 非常的相似。
- Coil 支持 androidX lifecycle 跟踪生命周期状态,也是是目前唯一支持 androidX lifecycle 的网络图片加载库。
- Coil 支持动态图片采样,假设本地有一个 500x500 的图片,当从磁盘读取 500x500 的映像时,我们将使用 100x100 的映像作为占位符。
如果你的是图片类型的应用,应用程序中包含了大量的图片,图片加载的速度是整个应用的核心指标之一,那么现在还不适合使用 Coil。
Coil 涵盖了 Glide、Picasso 等等图片加载库所支持的功能,除此之外 Coil 还有一个功能 动态图片采样。
动态图片采样
更多关于图片采样信息可以访问 Coil ,这里简单的说明一下,假设本地有一个 500x500 的图片,当从磁盘读取 500x500 的图片时,将使用 100x100 的映像作为占位符,等待加载完成之后才会完全显示,用官方的一张动图显示过程如下。
这种淡入动画效果,在视觉上体验非常舒适,占位符在主线程上设置,这样可以防止在 ImageView 为空的情况下出现白色闪烁,接下来让我们来看看如何使用 Coil?Coil、Glide 和 Picasso 使用上大比拼?
如何使用 Coil
添加 Coil 依赖
implementation "io.coil-kt:coil:0.11.0"
在 App moudule 下 build.gradle 文件中添加如下代码
kotlinOptions {
jvmTarget = "1.8"
}
在项目中调用你需要的代码,这里汇总了 Coil 所有使用方式
// 将图片加载到 ImageView 中, 并开启图片采样
// 用到了 Kotlin 的高级特性扩展,调用更加简单
imageView.load("https://www.example.com/image.jpg"){
crossfade(true)
}
inline fun ImageView.load(
uri: String?,
imageLoader: ImageLoader = Coil.imageLoader(applicationContext),
builder: LoadRequestBuilder.() -> Unit = {}
): RequestDisposable {
return imageLoader.load(context, uri) {
target(this@load)
builder()
}
}
// Coil 支持 Uri,File, String,HttpUrl, Bitmap, Drawable, DrawableId
imageView.load(R.drawable.ic_launcher_background)
imageView.load(File("/path/to/image.jpg"))
imageView.load("content://com.android.externalstorage/image.jpg")
// ......
// Coil 提供了四种转换: 模糊,圆形剪裁,灰度和圆角
imageView.load("https://www.example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.ic_launcher_background)
transformations(CircleCropTransformation())
}
// Coil 是一个 object 单例
val imageLoader1 = Coil.imageLoader(applicationContext)
// 可以创建 或者 调用第三方库(Koin) 注入自己的实例。
val imageLoader2 = ImageLoader(applicationContext)
// 在某些情况下,需要远程下载然后进行回调
var request = LoadRequest.Builder(applicationContext)
.data("https://www.example.com/image.jpg")
.target { drawable ->
// Handle the successful result.
}
.build()
imageLoader2.execute(request)
Coil、Glide 和 Picasso 使用上大比拼
Coil 基于 Kotlin 而设计,自然也拥有了 Kotlin 的高级函数的特性,使用比 Glide 和 Picasso 要简单很多。
基本使用
// Coil - 用到了 Kotlin 的高级特性扩展,一行代码加载图片
imageView.load(url)
// Glide
Glide.with(context)
.load(url)
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.into(imageView)
后台线程
// Coil:无阻塞和线程安全
val imageLoader = Coil.imageLoader(context)
val request = GetRequest.Builder(context)
.data(url)
.size(width, height)
.build()
val drawable = imageLoader.execute(request).drawable
// Glide:阻塞当前线程,一定不能从主线程调用
val drawable = Glide.with(context)
.load(url)
.submit(width, height)
.get()
// Picasso:阻塞当前线程,一定不能从主线程调用
val drawable = Picasso.get()
.load(url)
.resize(width, height)
.get()
自动检测 scaleType
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
// Coil:自动检测 scaleType
imageView.load(url) {
placeholder(placeholder)
}
// Glide
Glide.with(context)
.load(url)
.placeholder(placeholder)
.fitCenter()
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.placeholder(placeholder)
.fit()
.into(imageView)
Kotlin 小技巧
如何实现一行代码交换两个变量?我们先来回顾一下 JAVA 的做法
int a = 1;
int b = 2;
// JAVA - 中间变量
int temp = a;
a = b;
b = temp;
System.out.println("a = "+a +" b = "+b); // a = 2 b = 1
// JAVA - 加减运算
a = a + b;
b = a - b;
a = a - b;
System.out.println("a = " + a + " b = " + b); // a = 2 b = 1
// JAVA - 位运算
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println("a = " + a + " b = " + b); // a = 2 b = 1
// Kotlin
a = b.also { b = a }
println("a = ${a} b = ${b}") // a = 2 b = 1
参考文献
结语
致力于分享一系列 Android 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,请持续关注,除了翻译还有对每篇欧美文章思考,如果对你有帮助,请帮我点个赞,感谢!!!期待与你一起成长。
网友评论