本文內容較長,包含一個功能整個重構從想法到設計以及落地的完整過程,經過閱讀本文你能夠收穫:java
鑑於最近對原有項目進行了老的代碼的重構,其中的調用系統拍照選圖模塊就是咱們平常遇到一個痛點,須要在調用系統相機(相冊)的部分寫Intent :android
//示例代碼
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
activity.startActivityForResult(intent, getRequestCode());
複製代碼
在onActivityResult中得到照片之後、異步處理(方向糾正、壓縮等)得到的數據、若是有業務須要還需實現上傳到後端的邏輯:git
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable final Intent data) {
//示例代碼
//異步處理圖片、壓縮、旋轉等
File result = compressPhotoFromAlbumAsy(data);
// 上傳給後端、等業務處理
uploadAsy(result);
}
複製代碼
因此梳理下來有幾點痛點:github
故鑑於以上幾個痛點,我借鑑Glide中的幾個思想對此模塊進行了重構,設計了CoCo 庫->一行代碼靈活完成原生系統提供的拍照、選圖、壓縮、裁剪等脫離業務層的操做。先隨便貼兩個功能:面試
CoCo.with(this@MainActivity)
.take(createSDCardFile())
.start(object : CoCoCallBack<TakeResult> {
override fun onSuccess(data: TakeResult) {
iv_image.setImageBitmap(Utils.getBitmapFromFile(data.savedFile!!.absolutePath))
}
})
複製代碼
上述即可自動拿到原圖去展現咱們的結果,固然若是你須要壓縮或者處理一下原圖的話,只需切換一個操做符便可:編程
CoCo.with(this@TakePictureActivity)
.take(createSDCardFile())
//切換操做符執行下一步功能
.then()
//處理圖片
.dispose()
.start(object : CoCoCallBack<DisposeResult> {
override fun onSuccess(data: DisposeResult) {
iv_image.setImageBitmap(data.compressBitmap)
}
})
複製代碼
用dispose 操做符能夠壓縮並糾正原圖的轉向,而且異步操做自動像Glide同樣綁定with()中傳入容器的生命週期,並且dispose 對於圖片文件的處理方式能夠自定義,按需修改策略。後端
固然、CoCo還提供了 系統相冊選圖、系統裁剪等的功能、而且這一系列的操做符能夠組合使用,你能夠選圖、壓縮並裁剪、選圖裁剪等,按需本身組合便可。緩存
Glide 是咱們Android 經常使用的圖片庫,它有幾個特性是咱們所熟知的:markdown
1:上述使人舒服特性的原理在於在Glide 內部,本身經過添加Fragment的方式 在Fragment的生命週期中維護了個LifeCycle,所以能夠感知調用容器的生命週期,從而在相關的時候作一些異步的解綁操做。app
public class RequestManagerFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
}
}
複製代碼
2:而經過load操做符以後返回的對象實際上是一個 Builder對象,根據load入參數的不一樣產生不一樣的子類Builder對象,不一樣子類封裝本身特有的方法,好比load以後返回的是:DrawableTypeRequest 對象,本質也是一個Builder:
//簡化了繼承關係,只爲了展現清楚核心代碼
public class DrawableTypeRequest<ModelType> extends GenericRequestBuilder<ModelType> implements DownloadOptions {
/** * 本身特有的方法。。。。。 */
public <Y extends Target<File>> Y downloadOnly(Y target) {
///
}
}
複製代碼
調用asBitmap 以後又返回 BitmapTypeRequest 對象,一樣也是Builder:
//簡化了繼承關係,只爲了展現清楚核心代碼
public class BitmapTypeRequest<ModelType> extends GenericRequestBuilder<ModelType, Bitmap> {
/** * 本身特有的方法。。。。。 */
public BitmapRequestBuilder<ModelType, byte[]> toBytes() {
///
}
}
複製代碼
可是它們都繼承自最終基類 GenericRequestBuilder,這個類中就包含了咱們經常使用的into 方法:
public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable { /** * 基類共有的方法。。。。。 */
public <Y extends Target<TranscodeType>> Y into(Y target) {
////
}
}
複製代碼
經過上述設計便可以保證每一個子功能模塊在Builder中維護本身特有的方法,又能夠最終調用到父類的公用方法,一來比較符合面向對象的單一職責的設計原則,二來能夠提升代碼可讀性,讓使用者在使用的時候便於理解。
總結一下 Glide的核心思想就一行代碼完成
//在哪裏
Glide.with(this@MainActivity)
//幹嗎
.load("https://xxxxx.com.sxx.jpg")
//結果是
.into(iv_image)
複製代碼
我須要實現調用系統的相機、相冊、裁剪、無非都須要經過intent來啓動Activity,若是我採用Fragment的方式一來能夠將其做爲載體去啓動Activity和重寫onActivityResult接收返回的數據的方法,從而無須在業務層重寫,從而減小代碼入侵解決第一個痛點、而來能夠借鑑Glide模擬生命週期的方式在後期處理圖片好比壓縮、旋轉等異步操做的時候自動感知外部容器生命週期,從而減小業務代碼
Glide的 功能對應基類Builder提供基本方法、而後在子類拓展其特有功能的方法能夠借鑑到這個場景下:執行每一個功能產生相關的功能特性Builder、而後在父類方法中收攏,最終拿到結果,因此設計了以下UML圖:
下面以選圖功能做爲鏈路詳細分析整個大致設計(源碼有所簡化、方便理解):
//在哪裏 [activity中]
CoCo.with(this@MainActivity)
//幹嗎[選圖]
.pick()
//結果是 [PickResult]
.start(object : CoCoCallBack<PickResult> {
override fun onSuccess(data: PickResult) {
iv_image.setImageURI(data.originUri)
}
override fun onFailed(exception: Exception) {
}
})
複製代碼
第一步固然定義容器的場景:
object CoCo {
@JvmStatic
fun with(activity: Activity): FunctionManager {
checkStatusFirst(activity)
return FunctionManager.create(activity)
}
@JvmStatic
fun with(fragment: androidx.fragment.app.Fragment): FunctionManager {
checkStatusFirst(fragment.activity)
return FunctionManager.create(fragment)
}
@JvmStatic
fun with(fragment: Fragment): FunctionManager {
checkStatusFirst(fragment.activity)
return FunctionManager.create(fragment)
}
}
複製代碼
定義使用場景之後、返回一個FunctionManager 對象,裏面提供咱們全部已有的和之後能夠拓展的功能:
class FunctionManager(internal val container: IContainer) {
/** 拍照 * take photo from system camera * @param fileToSave the result of take photo to save * @see TakeBuilder */
fun take(fileToSave: File): TakeBuilder =
TakeBuilder(this).fileToSave(fileToSave)
/** 選圖 * select a photo from system gallery or file system * @see PickBuilder */
fun pick(): PickBuilder =
PickBuilder(this)
/** 異步處理文件 * dispose an file in background thread ,and will bind the lifecycle with current container * @param disposer you can also custom disposer * @see Disposer */
@JvmOverloads
fun dispose(disposer: Disposer = DefaultImageDisposer.get()): DisposeBuilder =
DisposeBuilder(this)
.disposer(disposer)
//........ expand other functions
}
複製代碼
當選用每個功能以後,咱們返回一個Builder、選圖對應當就是PickBuilder,而拍照對應當就是TakeBuilder,固然這都繼承自它的基類BaseFunctionBuilder:
abstract class BaseFunctionBuilder<Builder, Result>(
internal val functionManager: FunctionManager
) {
/** * 開始執行 */
fun start(callback: CoCoCallBack<Result>) {
generateWorker(getParamsBuilder()).start(null,callback)
}
/** * 獲取參數Builder */
internal abstract fun getParamsBuilder(): Builder
/** * 生成真正幹活兒的類 */
internal abstract fun generateWorker(builder: Builder): Worker<Builder, Result>
}
複製代碼
在這個基類裏面定義了兩個泛型參數 Builder 、Result、第一個Builder 參數的其具體實現用於封裝幹活時候的全部參數、一個用於承載最終完成的結果,咱們來看看PickBuilder的實現:
class PickBuilder(fm: FunctionManager) :
BaseFunctionBuilder<PickBuilder, PickResult>(fm) {
internal var pickRange = Range.PICK_DICM
/** * 在這裏拓展選圖特有的功能,好比是選擇系統相冊仍是 * @param pickRange the range you can choose * @see Range.PICK_DICM the system gallery * @see Range.PICK_CONTENT the system content file */
fun range(@PickRange pickRange: Int = Range.PICK_DICM): PickBuilder {
this.pickRange = pickRange
return this
}
override fun getParamsBuilder(): PickBuilder {
return this
}
override fun generateWorker(builder: PickBuilder): Worker<PickBuilder, PickResult> {
return PickPhotoWorker(functionManager.container, builder)
}
}
複製代碼
這裏生成Worker的最終實現是生成了一個 PickPhotoWorker,裏面完成了選圖的最終實現, 因此如今設計咱們的Worker類:
先設計接口抽象行爲:
interface Worker<Builder, ResultData> {
fun start( callBack: CoCoCallBack<ResultData>)
}
複製代碼
再設計一個抽象類,這一層去持有容器引用(好比咱們須要啓動Activity去選圖、也須要感知容器生命週期) 和 咱們構建好的Builder參數:
abstract class BaseWorker<Builder, ResultData>(val iContainer: IContainer, val mParams: Builder) :
Worker<Builder, ResultData>
複製代碼
再看看這個選圖的最終PickPhotoWorker:
class PickPhotoWorker(iContainer: IContainer, builder: PickBuilder) :
BaseWorker<PickBuilder, PickResult>(iContainer, builder) {
override fun start( callBack: CoCoCallBack<PickResult> ) {
val activity = iContainer.provideActivity()
activity ?: return
pickPhoto(activity, callBack)
}
private fun pickPhoto(activity: Activity, callBack: CoCoCallBack<PickResult>) {
val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
if (null === pickIntent.resolveActivity(activity.packageManager)) {
callBack.onFailed(BaseException("activity status error"))
return
}
try {
//start activity for result
iContainer.startActivityResult(
pickIntent, Constant.REQUEST_CODE_IMAGE_PICK
) { _: Int, resultCode: Int, data: Intent? ->
handleResult(resultCode, data, callBack)
}
} catch (e: Exception) {
callBack.onFailed(e)
}
}
private fun handleResult( resultCode: Int, intentData: Intent?, callBack: CoCoCallBack<PickResult> ) {
if (null != intentData && null != intentData.data) {
val result = PickResult()
result.originUri = intentData.data!!
callBack.onSuccess(result)
} else {
callBack.onFailed(BaseException("null result intentData"))
}
}
}
複製代碼
再看看容器的接口 iContainer:
interface IContainer {
//提供activity 判斷狀態、或者一些須要Context特有方法的場景
fun provideActivity(): Activity?
// startActivityResult 啓動activty、拿到回調結果
fun startActivityResult(intent: Intent, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit )
//得到外部容器宿主
fun getLifecycleHost(): Host
}
複製代碼
再看看它的具體實現固然也就是咱們實際用於承載一切的的Fragment:
class AcceptResultSupportFragment : Fragment(), IContainer,
Host {
/** * 當前生命週期狀態 */
private var current = Host.Status.INIT
private var requestCode: Int = 0
private var callback: ((requestCode: Int, resultCode: Int, data: Intent?) -> Unit)? = null
override fun startActivityResult( intent: Intent, requestCode: Int, callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit ) {
this.requestCode = requestCode
this.callback = callback
startActivityForResult(intent, requestCode)
}
override fun provideActivity(): Activity? = activity
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
callback!!(requestCode, resultCode, data)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//當外部想感知容器是否銷燬當時候,讀取這個變量便可
current = Host.Status.LIVE
}
override fun onDestroy() {
current = Host.Status.DEAD
super.onDestroy()
callback = null
}
override fun getStatus(): Int {
return current
}
override fun getLifecycleHost(): Host {
return this
}
}
複製代碼
而這個Fragment就是在咱們第一步CoCo.with 建立FunctionManager 的時候建立的。
到這裏,咱們最開始調用的每個操做符內部的基本鏈路就分析完成了、總結一下就是:
根據當前是在Activity或者Fragment的狀態建立一個功能提供器FunctionManager:
FunctionManager 選擇指定功能以後產生相應功能對於的Builder 用於拼裝各類參數
Builder參數拼裝完成後調用父類公共方法 start 去生成相應功能對應的Worker,每一個Worker去作本身的實現
Worker 作完成事情後回調給CallBack 拿到咱們的Result**
因此,以上述框架思想下,我拓展出了,系統拍照、系統選圖、系統裁剪、文件異步處理(壓縮、旋轉、等圖片操做)等總共四個方法,咱們只須要一行代碼便可完成,好比圖片處理:
CoCo.with(this)
.dispose()
.origin(imageFile.path)
.start(object : CoCoAdapter<DisposeResult>() {
override fun onSuccess(data: DisposeResult) {
iv_image.setImageBitmap(Utils.getBitmapFromFile(data.savedFile!!.absolutePath))
}
})
複製代碼
那麼如今你們可能有疑問了:
1. 裏面本身實現了LifeCycle 的感知, 谷歌已經有了LifeCycle 組件了,爲什麼還要重複造輪子?
2. 一般咱們拍照以後每每還須要對圖片進行壓縮處理以減少大小,那按照上述的設計,先拍照再處理豈不是要在回調裏面嵌套了?
So 回答第一個問題, 之因此不使用谷歌自帶的LifeCycle控件是由於, 這套控件只提供給support包或者Androidx 的包使用,而原生的Activity、Fragment是不具有lifeCycleOwner 的能力的,咱們不能保證用戶傳入的容器是 支持lifeCycleOwner 的仍是不支持的,因此本身實現以保證更好的兼容性。
第二個問題也就是接下來要重點介紹的切換操做符的設計緣由了,經過設計咱們的切換操做符,可讓咱們一行代碼組合多種操做,以知足實際業務場景,咱們每每拍完照以後須要進行壓縮處理,經過then操做符能夠一步到位:
好比,咱們選完一張圖片要進行裁剪而後再壓縮
CoCo.with(this@MainActivity)
//選圖
.pick()
//切換操做符
.then()
//裁剪
.crop()
//切換操做符
.then()
//壓縮處理
.dispose()
.start(object : CoCoAdapter<DisposeResult>() {
override fun onSuccess(data: DisposeResult) {
iv_image.setImageBitmap(data.compressBitmap)
}
})
複製代碼
效果圖:
按照以前的設計,咱們在調用 start的方法的時候,直接生成了其對應的Worker開始執行,那如今若是咱們想串聯多個功能是否是我只須要在切換其餘功能的操做符的時候,直接生成Woker對象存下來,最後完成全部的功能以後再依次按順序調用start、將上一個Woker處理的結果丟給下一個Woker便可完成功能的串聯?
class FunctionManager(internal val container: IContainer) {
internal val workerFlows = ArrayList<Any>()
}
複製代碼
fun then(): FunctionManager {
this.functionManager.workerFlows.add(generateWorker(getParamsBuilder()))
return this.functionManager
}
複製代碼
interface Worker<Builder, ResultData> {
fun start(formerResult: Any?, callBack: CoCoCallBack<ResultData>)
}
複製代碼
abstract class BaseFunctionBuilder<Builder, Result>(
internal val functionManager: FunctionManager
) {
fun then(): FunctionManager {
...
}
/** * call start to begin the workflow */
fun start(callback: CoCoCallBack<Result>) {
this.functionManager.workerFlows.add(generateWorker(getParamsBuilder()))
//循環取每個Worker
val iterator = functionManager.workerFlows.iterator()
if (!iterator.hasNext()) {
return
}
//第一次開始,第一個Worker formerResult 爲null
realApply(null, iterator, callback)
}
private fun realApply( formerResult: Any?, iterator: MutableIterator<Any>, callback: CoCoCallBack<Result> ) {
val worker: Worker<Builder, Result> = iterator.next() as Worker<Builder, Result>
worker.start(formerResult, object : CoCoCallBack<Result> {
override fun onSuccess(data: Result) {
if (iterator.hasNext()) {
iterator.remove()
//若是還有下個,遞歸繼續
realApply(data, iterator, callback)
} else {
//沒有下個,結束流程、回調給最終結果
callback.onSuccess(data)
}
}
override fun onFailed(exception: Exception) {
callback.onFailed(exception)
}
})
}
}
複製代碼
具體每一個子類實現就不細貼了,打擊有興趣能夠看看源碼,就是將上個處理的結果串聯起來,最終將Result一層一層傳到最終的Result中,至此咱們就完成了一行代碼完成裁剪、拍照、處理、以及它們功能的任意組合的同時、還保證了代碼的簡潔性、可讀性、易於拓展性。
CoCo 借鑑了Glide的語法和設計思想對系統經常使用功能如拍照、選圖、裁剪 作了一些封裝、不只能夠在處理圖片等異步操做時候感知外部容器,並且經過相似於RxJava工做流的思想,這幾個操做能夠串聯流式處理,大大提升了功能的拓展性和靈活性的同時,還能保證代碼的簡潔可讀性,能夠爲咱們平常開發減小不少臃腫代碼,提升了開發效率,目前CoCo穩定版上線已經支持了多個應用穩定性運行。
GitHub地址: