一款现代、高效的 Android 图片压缩框架
本项目主要基于 Android 自带的图片压缩 API 进行实现,提供了开源压缩方案 Luban 和 Compressor 的实现,解决了单一 Fie 类型数据源的问题,并在它们的基础之上进行了功能上的拓展。该项目的主要目的在于:提供一个统一图片压缩框库的实现,集成常用的两种图片压缩算法,让你以更低的成本集成图片压缩功能到自己的项目中。
1、项目背景:开源的图片压缩库
现在 Github 上的图片压缩框架主要有 Luban 和 Compressor 两个。Star 的数量也比较高,一个 9K,另一个 4K. 但是,这两个图片压缩的库有各自的优点和缺点。下面我们通过一个表格总结一下:
框架 | 优点 | 缺点 |
---|---|---|
Luban | 据说是根据微信图片压缩逆推的算法 | 1.只适用于一般的图片展示的场景,无法对图片的尺寸进行精准压缩;2.内部封装 AsyncTaks 来进行异步的图片压缩,对于 RxJava 支持不好。3.只支持单一 File 类型数据源和结果类型,应用场景有限。 |
Compressor | 1.可以对图片的尺寸进行精准压缩;2.支持 RxJava。 | 1.尺寸压缩的场景有限,如果有特别的需求,则需要手动修改源代码;2.图片压缩采样的时候计算有问题,导致采样后的图片尺寸总是小于我们指定的尺寸;3.只支持单一 File 类型数据源和结果类型,应用场景有限。 |
以上,我们总结了 Github 上面的两款比较受欢迎的开源图片压缩库的优缺点。正如我们上面所说,我们希望能够综合它们的优点,并且解决以上两款开源库设计不好的地方。因此,你可以通过下文来了解我们的库所提供的功能,以及如何在您的项目中接入我们的压缩方案。
2、功能和特性
目前,我们的库已经支持了下面的功能,在后面的介绍中,我们会介绍如何在项目中进行详细配置和使用:
-
支持 Luban 压缩方案:据介绍这是微信逆推的压缩算法,它在我们的项目中只作为一种可选的压缩方案,除此之外您还可以使用 Compressor 进行压缩,以及自定义压缩策略。
-
支持 Compressor 压缩方案:这种压缩方案综合了 Android 自带的三种压缩方式,可以对压缩结果的尺寸进行精确的控制。此外,在我们的项目中,我们对这种压缩方案的功能进行了拓展,不仅支持了颜色通道的选择,还提供了多种可选的策略,用来对尺寸进行更详细的配置。
-
支持 RxJava 的方式进行压缩:使用 RxJava 的方式,您可以任意指定压缩任务和结果回调任务所在的线程,在我们的库中,我们提供了一个 Flowable 类型的对象,您可以用它来进行后续的处理。
-
支持 AsyncTask + 回调的压缩方式:这种方式通过使用 AsyncTask 在后台线程中执行压缩任务,当获取到压缩结果的时候通过 Handler 在主线程中返回压缩结果。
-
提供了同步的压缩方式:当然,有时候我们并不需要使用回调或者 RxJava 执行异步任务。比如,当我们本身已经处于后台线程的时候,我们希望的只是在当前线程中直接执行压缩任务并拿到压缩结果。因此,为了让我们的库适用于更多的应用场景,我们提出了这种压缩方案。
-
支持多种数据源:在上面的两款开源库中,要求传入的资源类型是 File。这就意味着,当我们从相机中获取到原始的图片信息(通常是字节数组)的时候,我们不得不先将其写入到文件系统中,然后获取到 File 的时候再进行压缩。这是没必要的,并且无疑地,会带来性能上的损耗。因此,为了能让我们的库应用到更多的场景中,我们支持了多种数据源。目前支持的数据源包括:文件类型 File,原始图片信息 byte[] 以及 Bitmap。
-
支持多种结果类型:以上两款开源库还存在一个问题,即返回的结果的类型也只支持 File 类型。但很多时候,我们希望传入的是 Bitmap,处理之后传出的结果也是 Bitmap. 因此,为了让我们的库适用于这种场景,我们也支持 Bitmap 类型的返回结果。
-
提供用户自定义压缩算法的接口:我们希望设计的库可以允许用户自定义压缩策略。在想要替换图片压缩算法的时候,通过链式调用的一个方法直接更换策略即可。即,我们希望能够让用户以最低的成本替换项目中的图片压缩算法。
想要进一步了解该库的特性和功能,你还可以使用我们提供的示例 APK
sample_preview.jpg3、使用
3.1 在 Gradle 中引用我们的库
在项目中接入我们的库是非常简单的。首先,在项目的 Gradle 中加入 jcenter仓库:
repositories {
jcenter()
}
然后,在项目的依赖中添加该库的依赖:
implementation 'me.shouheng.compressor:compressor:1.3.0'
3.2 使用我们库进行压缩
详细的用法你可以参考我们提供的 Sample 程序,这里我们介绍下使用我们库的几个要点:
首先,你要使用 Compress 类的静态方法获取一个 Compress 实例,这是所有配置的起点。针对不同的数据源,你需要调用它的三个不同的工厂方法:
// 使用文件 File 获取 Compress 实例
val compress = Compress.with(this, file)
// 使用图片的字节数组获取 Compress 实例
val compress = Compress.with(this, byteArray)
// 使用图片的 Bitmap 获取 Compress 实例
val compress = Compress.with(this, bitmap)
然后,你可以调用 Compress 的实例方法来对压缩的参数进行基本的配置:
compress
// 指定要求的图片的质量
.setQuality(60)
// 指定文件的输出目录(如果返回结果不是 File 的会,无效)
.setTargetDir(PathUtils.getExternalPicturesPath())
// 指定压缩结果回调(如哦返回结果不是 File 则不会被回调到)
.setCompressListener(object : CompressListener {
override fun onStart() {
LogUtils.d(Thread.currentThread().toString())
ToastUtils.showShort("Start [Compressor,File]")
}
override fun onSuccess(result: File?) {
LogUtils.d(Thread.currentThread().toString())
displayResult(result?.absolutePath)
ToastUtils.showShort("Success [Compressor,File] : $result")
}
override fun onError(throwable: Throwable?) {
LogUtils.d(Thread.currentThread().toString())
ToastUtils.showShort("Error [Compressor,File] : $throwable")
}
})
以上是使用 Compress 进行基本的配置,上面我们给出了一些注释。关于各个方法的含义,后续我们会进行详细的介绍。
根据上述配置,我们就得到了一个 Compress 对象。然后,我们需要指定一个图片压缩策略,并调用压缩策略的方法进行更详细的配置。以 Compressor 为例,我们可以调用 Strategies.compressor() 方法获取它的实例:
val compressor = compress
.strategy(Strategies.compressor())
.setConfig(config)
.setMaxHeight(100f)
.setMaxWidth(100f)
.setScaleMode(scaleMode)
按照上述配置,我们就拿到了 Compressor 实例。当然如果你的配置使用比较频繁,为了简化代码,你可以把这些配置过程放在一个工具方法中,而无需每次都进行这些配置。
下面就是触发图片压缩并获取压缩结果的过程了。
上面我们也提到过,针对 File 类型和 Bitmap 类型的返回结果,我们提供了两个方案。默认的返回类型是 File,为了得到 Bitmap 类型的结果,你只需要调用一下 Compressor 实例的 asBitmap()
方法,这样整个流程就‘拐’到了 Bitmap 的构建中去了。
最终触发图片压缩有三种方式,
// 方式 1:使用 AsyncTask 压缩,此时只能通过之前的回调获取压缩结果
compressor.launch()
// 方式 2:将压缩任务转换成 Flowable,使用 RxJava 指定任务的线程和获取结果的线程
val d = compressor
.asFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
ToastUtils.showShort("Success [Compressor,File,Flowable] $it")
displayResult(it.absolutePath)
}, {
ToastUtils.showShort("Error [Compressor,File,Flowable] : $it")
})
// 方式 3:直接在当前线程中获取返回结果(同步,阻塞)
val resultFile = compressor.get()
对于 Luban 压缩方式的使用与之类似,只需要在指定压缩策略的那一步中将策略替换成 luban 即可。另外,对于自定义图片压缩的方式也是类似的,只需要在指定策略的那一步骤中指定即可。
因此,如果使用的是 RxJava 的方式获取压缩结果,并且输入类型是 File,输出类型是 Bitmap,整个压缩的流程将是下面这样:
val compressor = Compress.with(this@MainActivity, file)
.strategy(Strategies.compressor())
.setConfig(config)
.setMaxHeight(100f)
.setMaxWidth(100f)
.setScaleMode(scaleMode)
.asBitmap()
.asFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
ToastUtils.showShort("Success [Compressor,Bitmap,Flowable] $it")
displayResult(it)
}, {
ToastUtils.showShort("Error [Compressor,Bitmap,Flowable] : $it")
})
3.3 你可能需要注意的事项
-
如果你期望返回的结果是 Bitmap,那么下面这些方法传入的参数将不会起作用:
-
setQuality(/*...*/)
:用来指定输出图片的质量,因为我们在调用 Bitmap.compress() 方法的时候才能用到该参数,因此它不会起作用 -
setTargetDir(/*...*/)
:用来指定输出文件的位置 -
setCompressListener(/*...*/)
:用来获取文件的压缩结果的回调
-
-
当你使用 Luban 算法压缩 Bitmap 的时候,内部会采用类似于 Compressor 的压缩逻辑,但是输出的结果与传入 File 类型时一致。这是因为 Luban 的逻辑无非就是用来采样,而采样是在加载到内存中的时候进行的,因为输入本身已经是 Bitmap,因此无法按照原来的逻辑进行压缩。
-
Luban 不支持 Bitmap 的色彩配置,因为本身没有创建 Bitmap。对于 Compressor 的色彩,虽然所有参数都可以使用,但是在使用的时候可能会出现不支持的情况。此时,不会导致程序 Crash,错误的信息会通过回调或者 RxJava 回调出来。
-
1.0.0 到 1.3.0 的迁移:我们已经尽可能适配之前的方案,但是仍然有一部分内容我们做了调整。您可以按照下面的步骤做迁移工作:
- Compressor 的压缩策略从 Configuration 中移动到了 ScaleMode 中。
- 重命名 Configuration 类为 Config,因为它可能会与您项目中其他的代码命名冲突而不得不指定包名称。
- 一些工具类命名调整,您的项目中一般不会用到它们,但是如果用到了注意调整下名称。
4、项目资料
如果您对该项目感兴趣并且希望为该项目共享您的代码,那么您可以通过下面的一些资料来了解相关的内容:
- 项目整体的架构设计:https://www.processon.com/view/link/5cdfb769e4b00528648784b7
- Android 图片压缩 API 的介绍,该项目的简介等:《开源一个 Android 图片压缩框架》
- 我们提供的示例 APK:app-debug.apk
更新日志
-
版本 1.3.0:
- 增加了多种数据源的支持
- 增加了 Bitmap 返回类型的支持
- 增加了同步压缩
- 增加了色彩的支持
- 部分代码重构,Compressor 压缩策略参数位置调整
- 项目结构调整
-
版本 1.0.0:修改了 Compressor 压缩模式图片无法旋转的问题
关于作者
你可以通过访问下面的链接来获取作者的信息: