阅读 1557

用这个库解决你的图片压缩和加载问题

在 Android 应用中,图片是占用内存资源比较多的一部分。如何在应用当中加载并处理图片的同时又能够保证程序响应的效率呢?你可以使用我开发的这个库:Compressor

随着新版本 1.3.5 的发布,这个库已经支持了非常多的功能,并且支持在 Kotlin 协程中获取压缩的结果。之所以添加这些功能是因为在之前我做的应用都是与 CV 相关的,所以不可避免地要面对林林种种的应用场景。能够支持更多的应用场景,这也是这个库与其他的很多框架不同的地方。

下面就通过示例一一介绍下如何使用该库处理各种应用场景。

1、该库的各种应用场景示例

1.1 支持更多的数据源类型:处理相机中获取到的数据

很多图片压缩库会将 File 图片文件作为压缩的目标,但有些情况下我们拿到的数据并不是 File 类型的。比如,我们从相机当中获取到的图片就是字节数组 byte[] 类型的。此时,按照其他的库的处理方式,你要先将文件写入到磁盘上,然后再从磁盘中读取 File 进行压缩。如果你对应用的性能有追求的话,这显然会导致程序响应变慢,从而影响用户体验。而在我们的库中,你可以直接将图片的字节数组作为参数,我们会将其转换成 Bitmap 并进行压缩处理:

图片处理方式

有些时候,你拿到的图片也不是 byte[] 类型的而是 Bitmap. 比如,你加载了一张图片到内存中,然后你希望使用这个图片的 Bitmap 进行操作而不是每次都从磁盘上进行加载。此时你可能希望有一个框架能够直接使用 Bitmap 进行压缩。这个时候,其他框架就帮不了你太大的忙了。当然,你也可以直接使用 Bitmap 相关的 API 自己写一套压缩逻辑。只不过这样你的代码中就不得不充斥着大量重复的逻辑。既然有一个框架可以帮你解决这个问题,为什么不试一下呢?

使用数据源的三种方式:

// 方式 1:使用文件 File 获取
val compress = Compress.with(this, file)
// 方式 2:使用图片的字节数组获取
val compress = Compress.with(this, byteArray)
// 方式 3:使用图片的 Bitmap 获取
val compress = Compress.with(this, bitmap)
复制代码

上面的代码将返回一个类型为 Compress 的对象。它提供了几个 Builder 方法,你可以使用链式的调用为自己的图片压缩做基本的配置:

compress
    // 指定要求的图片的质量
    .setQuality(60)
    // 指定文件的输出目录
    .setTargetDir(PathUtils.getExternalPicturesPath())
复制代码

1.2 支持更多结果类型:对压缩后的图像处理的问题

我想下图这样的应用并不少见,你需要一个图像最终的效果显示在中间,同时下图有多个预览的效果:

应用示例

很多框架传入的参数是 File 类型的,得到的结果也是 File 类型的。这就意味着,假如你从磁盘上面读取了一个图片并对其进行压缩,你需要将其写入到磁盘上面之后,再从磁盘上面读取并得到 Bitmap,然后才能继续使用它进行后续的图像处理。这会浪费一定的时间,并且会使代码变得冗长、晦涩。在我们的框架中,你可以直接将图片压缩的结果指定为 Bitmap. 这样你就可以直接使用压缩之后的图片进行后续处理了。

压缩结果获取

在我们的框架中,将图片压缩的结果从 File 变成 Bitmap 是非常简单的。就像 Glide 一样,你只需要调用一下 asBitmap() 方法,然后我们就会把 Bitmap 类型的结果返回给你(不调用的话,默认返回是 File 类型的结果):

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({
        // 这里返回的 it 就是 Bitmap 类型的
    }, {
        // 错误回调
    })
复制代码

1.3 支持更多结果获取方式

1. 同步和异步兼容的结果获取方式

一个框架如果只提供了接口回调或者即使是 RxJava 形式的回调都有可能会导致你的代码结构变得复杂。比如:

val compressor = Compress.with(this@MainActivity, file)
    .strategy(Strategies.compressor())
    .asFlowable()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
        // 获取到结果之后的逻辑 --- 1
    }, {
        // 错误回调            --- 2
    })
复制代码

以 RxJava 为例,在上面的代码结构中,获取到了结果之后需要在 RxJava 的注册监听里面继续处理。这就使得你的压缩后的业务代码必须写在上面的代码 1 处。

如果本身上面代码就处在主线程中还好,但如果其已经处在异步的线程当中,这就意味着你在异步线程中起了一个异步任务,然后又将结果 post 到主线程当中处理。在这种情形中,我们有没有办法直接在这个异步线程中通过阻塞的方式获取到结果呢?Compressor 告诉你是可以的。

此时,你配置完成了之后,只需要调用 get() 方法而不是 asFlowable() 方法,就可以直接在当前线程中获取到压缩结果:

val bitmap = Compress.with(this@MainActivity, file)
    .strategy(Strategies.compressor())
    .setConfig(config)
    .setMaxHeight(100f)
    .setMaxWidth(100f)
    .setScaleMode(scaleMode)
    .asBitmap()
    .get()
复制代码

2. 对 Kotlin 协程的支持

在新版本 1.3.5 上,我将整个程序从 Java 切换到了 Kotlin,并且增加了对 Kotlin 协程的支持。跟上面的例子类似,你只需要配置完成之后,获取结果的时候,调用 get() 的重载方法并指定一个 CoroutineContext 即可:

GlobalScope.launch {
    // 通过协程获取程序的执行结果
    val resultFile = Compress.with(this@MainActivity, file)
        .strategy(Strategies.compressor())
        .setConfig(config)
        .setMaxHeight(100f)
        .setMaxWidth(100f)
        .setScaleMode(scaleMode)
        .asBitmap()
        .get(Dispatchers.IO)
}
复制代码

1.4 支持的图片压缩算法

1. 还是那两个图片算法

又回到之前的那个问题,现在很多人使用 Luban 进行图片压缩,但是这个库本质上还是调用 Android 的图片压缩 API 进行处理,所以最终还是按照 2 的整数次幂进行采样。因为其是逆推的算法,所以无法保证最终输出的图片的大小,其应用场景比较有限,而且不是很严谨。另外是 Compressor 压缩策略,它可以将图片压缩到固定的大小,但是它的压缩只满足了一种情形(我们的库对其进行了拓展,稍后我们会进行说明)。

在我们的库当中你只需要在构造 Compress 的时候使用 Streategies 类的两个静态方法就可以直接使用这两个压缩策略,而后你可以继续通过方法链对两个压缩策略进行更加详细的定制:

// 使用 Compressor 压缩策略
val bitmap = Compress.with(this@MainActivity, file)
    .strategy(Strategies.compressor())
    .....

// 使用 Luban 压缩策略
val bitmap = Compress.with(this@MainActivity, file)
    .strategy(Strategies.luban())
    .....
复制代码

2. Compressor 策略的说明

在之前我们对图像进行文字识别的时候,为了找到一个图像大小和识别准确率之间的一个折衷的方案,我们进行了大量的试验并得出结论:短变在 1100 左右的图像的识别效果比较理想。如果你采用开源的 Luban 或者 Compressor,它们都无法满足你这个需求。在我们的库当中你可以使用 Comressor 策略并且指定 ScaleMode 为 SCALE_SMALLER 就可以短变为基准进行压缩。对于 ScaleMode 的四种模式需要说明下:

ScaleMode

也就是,

  • SCALE_LARGER:对高度和长度中较大的一个进行压缩,另一个自适应,因此压缩结果是 (W:100, H:50). 也就是说,因为原始图片宽高比 2:1,我们需要保持这个宽高比之后再压缩。而目标宽高比是 1:1. 而原图的宽度比较大,所以,我们选择将宽度作为压缩的基准,宽度缩小 10 倍,高度也缩小 10 倍。这是 Compressor 库的默认压缩策略,显然它只是优先使得到的图片更小。这在一般情景中没有问题,但是当你想把短边控制在 100 就无计可施了(需要计算之后再传参),此时可以使用 SCALE_SMALLER。
  • SCALE_SMALLER:对高度和长度中较大的一个进行压缩,另一个自适应,因此压缩结果是 (W:200, H:100). 也就是,高度缩小 5 倍之后,达到目标 100,然后宽度缩小 5 倍,达到 200.
  • SCALE_WIDTH:对宽度进行压缩,高度自适应。因此得到的结果与 SCALE_LARGER 一致。
  • SCALE_HEIGHT:对高度进行压缩,宽度自适应,因此得到的结果与 SCALE_HEIGHT 一致。

3. 自定义策略

除了使用我们提供的预定义压缩策略,还可以自己定义图片压缩策略。比如:

class MySimpleStrategy: SimpleStrategy() {

    override fun calInSampleSize(): Int {
        return 2
    }
}
复制代码

然后在构造 Compress 的时候将自定义的策略的实例传入即可。

2、总结和说明

这里对该图片压缩库的一些应用场景进行了介绍。我们满足了非常多的应用场景,而且很多场景,比如获取结果、传入数据源等的时候,你只需要改变一个参数或者更改一个方法就可以了。想知道它是怎么设计和实现的吗?那就参考下源码吧。个人觉得这个库的设计还算是得意之作,尤其是从只支持 File 到兼容 Bitmap 的那次改造。下面是库的整体的结构设计:

压缩库结构

最后,欢迎大家 Star/Fork 和使用该库,感谢!

获取更多技术文章可以直接关注我的公众号「Code Brick」,另外感兴趣的可以加入技术 QQ 交流群:1018235573.

公众号信息

以上,感谢阅读~

关注下面的标签,发现更多相似文章
评论