本項目主要基於 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
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、項目資料
如果您對該項目感興趣並且希望爲該項目共享您的代碼,那麼您可以通過下面的一些資料來了解相關的內容:
- 項目整體的架構設計:https://www.processon.com/view/link/5cdfb769e4b00528648784b7
- Android 圖片壓縮 API 的介紹,該項目的簡介等:《開源一個 Android 圖片壓縮框架》
- 我們提供的示例 APK:app-debug.apk
更新日誌
-
版本 1.3.0:
- 增加了多種數據源的支持
- 增加了 Bitmap 返回類型的支持
- 增加了同步壓縮
- 增加了色彩的支持
- 部分代碼重構,Compressor 壓縮策略參數位置調整
- 項目結構調整
-
版本 1.0.0:修改了 Compressor 壓縮模式圖片無法旋轉的問題
關於作者
你可以通過訪問下面的鏈接來獲取作者的信息: