阅读 4136

[译][1.4K+ Star] Kotlin 新秀 Coil、Glide 和 Picasso 大比拼

前言

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 张图片并以网格布局显示它们,如下图所示。

所有图片都是在同一时间可见的,几乎是在同一时间被请求的,因此,可以认为它们是并行加载。

  • 测试加载每张图片所需的时间:为了获得更准确的数据,图片被下载了十次将取平均值。
  • 计算加载图片列表所需的时间:这个数字很重要,因为图片是并行加载的,所以不能从单个图片推断。这个测试也做了十次测试将取平均值。

另一个需要测试的重要点是第一次加载图片和从缓存中加载它们所花费的时间,已经进行了多次测试,覆盖了上面的场景。

从网络下载

我们开始第一个场景,当缓存为空时,从网络中下载图片。

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 的映像作为占位符,等待加载完成之后才会完全显示,用官方的一张动图显示过程如下。

img

这种淡入动画效果,在视觉上体验非常舒适,占位符在主线程上设置,这样可以防止在 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)
复制代码

Coil 实战

Jetpack 实战项目 PokemonGo(神奇宝贝)基于 MVVM 架构和 Repository 设计模式开发的一个小型的 App 项目,涉及到技术:Paging3(network + db),Dagger-Hilt,App Startup,DataBinding,Room,Motionlayout,Kotlin Flow,Coil 等等。

在这个项目中加载图片使用了 Coil ,可以去下载体验一下。

仓库地址:https://github.com/hi-dhl/PokemonGo

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 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,请持续关注,除了翻译还有对每篇欧美文章思考,如果对你有帮助,请帮我点个赞,感谢!!!期待与你一起成长。

计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,目前已经包含了 App Startup、Paging3、Hilt 等等,正在逐渐增加其他 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。

算法

由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序

  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……

每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长

Android 10 源码系列

正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库

Android 应用系列

精选译文

工具系列

逆向系列

文章分类
Android
文章标签