一款现代、高效的 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

一款现代、高效的 Android 图片压缩框架_第1张图片

3、使用

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、项目资料

如果您对该项目感兴趣并且希望为该项目共享您的代码,那么您可以通过下面的一些资料来了解相关的内容:

  1. 项目整体的架构设计:https://www.processon.com/view/link/5cdfb769e4b00528648784b7
  2. Android 图片压缩 API 的介绍,该项目的简介等:《开源一个 Android 图片压缩框架》
  3. 我们提供的示例 APK:app-debug.apk

更新日志

  • 版本 1.3.0:

    • 增加了多种数据源的支持
    • 增加了 Bitmap 返回类型的支持
    • 增加了同步压缩
    • 增加了色彩的支持
    • 部分代码重构,Compressor 压缩策略参数位置调整
    • 项目结构调整
  • 版本 1.0.0:修改了 Compressor 压缩模式图片无法旋转的问题

关于作者

你可以通过访问下面的链接来获取作者的信息:

  1. Twitter: https://twitter.com/shouheng_wang
  2. 微博:https://weibo.com/5401152113/profile?rightmod=1&wvr=6&mod=personinfo
  3. Github: https://github.com/Shouheng88
  4. 掘金:https://juejin.im/user/585555e11b69e6006c907a2a

项目地址:https://github.com/Shouheng88/Compressor

你可能感兴趣的:(Android,基础,进阶)