一款現代、高效的 Android 圖片壓縮框架

本項目主要基於 Android 自帶的圖片壓縮 API 進行實現,提供了開源壓縮方案 LubanCompressor 的實現,解決了單一 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.jpg

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章