原标题:推荐一个好用的拍照选图库,致敬Glide
本文作者
作者:思忆_GeorgeQin
1
内容提要
本文内容较长,包含一个功能整个重构从想法到设计以及落地的完整过程,通过阅读本文你可以收获:
- Glide 几个关键特性的设计原理以及对它们的思考(面试可用)
- 编码从拓展性层面的考虑到面向对象编程的实践
- 类似于RxJava的工作流的设计思想以及实践
- 一些kotlin和Java 互相调用的小技巧
- 一个功能强大的调用系统相机、系统相册的库以及它是如何被设计出来的
2
背景
鉴于最近对原有项目进行了老的代码的重构,其中的调用系统拍照选图模块就是我们日常遇到一个痛点,需要在调用系统相机(相册)的部分写Intent :
//示例代码
Intent intent = newIntent(Intent.ACTION_GET_CONTENT, null);
intent.setType( “image/*”);
activity.startActivityForResult(intent, getRequestCode);
在onActivityResult中获得照片以后、异步处理(方向纠正、压缩等)获得的数据、如果有业务需要还需实现上传到后端的逻辑:
@Override
protectedvoidonActivityResult( intrequestCode, intresultCode, @Nullable finalIntent data) {
//示例代码
//异步处理图片、压缩、旋转等
File result = compressPhotoFromAlbumAsy(data);
// 上传给后端、等业务处理
uploadAsy(result);
}
所以梳理下来有几点痛点:
- 处理入口分散、触发拍照行为和接收不在一个地方、依赖重写onActivityResult方法,非常不利于后期模块化组件化拆分。
- 图片需要异步处理以提高用户体验,所以此时还需要考虑容器的生命周期,需要添加很多判断代码,从而导致可读性比较差,每次梳理图片的逻辑处理和业务上的图片上传层都需要不少时间
重构结果:
故鉴于以上几个痛点,我借鉴Glide中的几个思想对此模块进行了重构,设计了CoCo 库->一行代码灵活完成原生系统提供的拍照、选图、压缩、裁剪等脱离业务层的操作。先随便贴两个功能:
https://github.com/soulqw/CoCo
调用系统相机拍张照:
CoCo.with( this@MainActivity)
.take(createSDCardFile)
.start( object: CoCoCallBack<TakeResult> {
overridefunonSuccess( data: TakeResult) {
iv_image.setImageBitmap(Utils.getBitmapFromFile( data.savedFile!!.absolutePath))
}
})
上述便可自动拿到原图去展示我们的结果,当然如果你需要压缩或者处理一下原图的话,只需切换一个操作符即可:
CoCo.with( this@TakePictureActivity)
.take(createSDCardFile)
//切换操作符执行下一步功能
.then
//处理图片
.dispose
.start( object: CoCoCallBack<DisposeResult> {
overridefunonSuccess( data: DisposeResult) {
iv_image.setImageBitmap( data.compressBitmap)
}
})
用dispose 操作符可以压缩并纠正原图的转向,并且异步操作自动像Glide一样绑定with中传入容器的生命周期,而且dispose 对于图片文件的处理方式可以自定义,按需修改策略。
当然、CoCo还提供了 系统相册选图、系统裁剪等的功能、并且这一系列的操作符可以组合使用,你可以选图、压缩并裁剪、选图裁剪等,按需自己组合即可。
3
原理与设计思想
对Glide 的致敬:
Glide 是我们Android 常用的图片库,它有几个特性是我们所熟知的:
- 一行代码完成图片加载、缓存、展示过程、并且异步操作自动绑定容器生命周期
- 执行加载操作load之后、拓展了很多不同的方法可灵活选择,比如图片大小、缓存类型等、 最终通过into 完成所有操作
1:上述令人舒服特性的原理在于在Glide 内部,自己通过添加Fragment的方式 在Fragment的生命周期中维护了个LifeCycle,因此可以感知调用容器的生命周期,从而在相关的时候做一些异步的解绑操作。
publicclassRequestManagerFragmentextendsFragment{
@Override
publicvoidonStart{
super.onStart;
lifecycle.onStart;
}
@Override
publicvoidonDestroy{
super.onDestroy;
lifecycle.onDestroy;
}
}
2:而通过load操作符之后返回的对象其实是一个 Builder对象,根据load入参数的不同产生不同的子类Builder对象,不同子类封装自己特有的方法,比如load之后返回的是:DrawableTypeRequest 对象,本质也是一个Builder:
//简化了继承关系,只为了展示清楚核心代码
publicclassDrawableTypeRequest< ModelType> extendsGenericRequestBuilder< ModelType> implementsDownloadOptions{
/**
* 自己特有的方法。。。。。
*/
public<Y extends Target<File>> Y downloadOnly(Y target){
///
}
}
调用asBitmap 之后又返回 BitmapTypeRequest 对象,同样也是Builder:
//简化了继承关系,只为了展示清楚核心代码
publicclassBitmapTypeRequest<ModelType> extendsGenericRequestBuilder<ModelType, Bitmap> {
/**
* 自己特有的方法。。。。。
*/
publicBitmapRequestBuilder<ModelType, byte[]> toBytes {
///
}
}
但是它们都继承自最终基类 GenericRequestBuilder,这个类中就包含了我们常用的into 方法:
publicclassGenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implementsCloneable{ /**
* 基类共有的方法。。。。。
*/
public<Y extends Target<TranscodeType>> Y into(Y target){
////
}
}
通过上述设计即可以保证每个子功能模块在Builder中维护自己特有的方法,又可以最终调用到父类的公用方法,一来比较符合面向对象的单一职责的设计原则,二来可以提高代码可读性,让使用者在使用的时候便于理解。
总结一下 Glide的核心思想就一行代码完成
- 在哪里?
- 做什么?
- 结果是?
//在哪里
Glide. with( this@MainActivity)
//干嘛
.load( “https://xxxxx.com.sxx.jpg”)
//结果是
.into(iv_image)
需求分析
1. 我需要实现调用系统的相机、相册、裁剪、无非都需要通过intent来启动Activity,如果我采用Fragment的方式一来可以将其作为载体去启动Activity和重写onActivityResult 接收返回的数据的方法。
从而无须在业务层重写,从而减少代码入侵解决第一个痛点、二来可以借鉴Glide 模拟生命周期的方式在后期处理图片比如压缩、旋转等异步操作的时候自动感知外部容器生命周期,从而减少业务代码 。
2. Glide的 功能对应基类Builder提供基本方法、然后在子类拓展其特有功能的方法可以借鉴到这个场景下: 执行每个功能产生相关的功能特性Builder、然后在父类方法中收拢,最终拿到结果,所以设计了如下UML图:

UML 分析:
4
详细源码的设计与分析
下面以选图功能作为链路详细分析整个大体设计(源码有所简化、方便理解):
//在哪里 [activity中]
CoCo.with( this@MainActivity)
//干嘛[选图]
.pick
//结果是 [PickResult]
.start( object: CoCoCallBack<PickResult> {
overridefunonSuccess( data: PickResult) {
iv_image.setImageURI( data.originUri)
}
overridefunonFailed(exception: Exception) {
}
})
第一步当然定义容器的场景:
objectCoCo {
@JvmStatic
funwith(activity: Activity) : FunctionManager {
checkStatusFirst(activity)
returnFunctionManager.create(activity)
}
@JvmStatic
funwith(fragment: androidx. fragment. app. Fragment) : FunctionManager {
checkStatusFirst(fragment.activity)
returnFunctionManager.create(fragment)
}
@JvmStatic
funwith(fragment: Fragment) : FunctionManager {
checkStatusFirst(fragment.activity)
returnFunctionManager.create(fragment)
}
}
定义使用场景以后、返回一个FunctionManager 对象,里面提供我们所有已有的和以后可以拓展的功能:
小技巧:使用 @JvmStatic 注解可以保持Java 调用 kotlin 代码时 和kotlin 自己调用提供一致性的体验
classFunctionManager( internalvalcontainer: IContainer) {
/** 拍照
* take photo from system camera
* @paramfileToSave the result of take photo to save
* @seeTakeBuilder
*/
funtake(fileToSave: File) : TakeBuilder =
TakeBuilder( this).fileToSave(fileToSave)
/** 选图
* select a photo from system gallery or file system
* @seePickBuilder
*/
funpick: PickBuilder =
PickBuilder( this)
/** 异步处理文件
* dispose an file in background thread ,and will bind the lifecycle with current container
* @paramdisposer you can also custom disposer
* @seeDisposer
*/
@JvmOverloads
fundispose(disposer: Disposer= DefaultImageDisposer. get): DisposeBuilder =
DisposeBuilder( this)
.disposer(disposer)
//…….. expand other functions
}
当选用每一个功能之后,我们返回一个Builder、选图对应当就是PickBuilder,而拍照对应当就是TakeBuilder,当然这都继承自它的基类BaseFunctionBuilder:
abstractclassBaseFunctionBuilder< Builder, Result> (
internalvalfunctionManager: FunctionManager
) {
/**
* 开始执行
*/
funstart(callback: CoCoCallBack< Result>) {
generateWorker(getParamsBuilder).start( null,callback)
}
/**
* 获取参数Builder
*/
internalabstractfungetParamsBuilder: Builder
/**
* 生成真正干活儿的类
*/
internalabstractfungenerateWorker(builder: Builder) : Worker<Builder, Result>
}
在这个基类里面定义了两个泛型参数 Builder 、Result、第一个Builder 参数的其具体实现用于封装干活时候的所有参数、一个用于承载最终完成的结果,我们来看看PickBuilder的实现:
classPickBuilder(fm: FunctionManager) :
BaseFunctionBuilder<PickBuilder, PickResult>(fm) {
internalvarpickRange = Range.PICK_DICM
/**
* 在这里拓展选图特有的功能,比如是选择系统相册还是
* @parampickRange the range you can choose
* @seeRange.PICK_DICM the system gallery
* @seeRange.PICK_CONTENT the system content file
*/
funrange( @PickRangepickRange: Int= Range.PICK_DICM) : PickBuilder {
this.pickRange = pickRange
returnthis
}
overridefungetParamsBuilder: PickBuilder {
returnthis
}
overridefungenerateWorker(builder: PickBuilder) : Worker<PickBuilder, PickResult> {
returnPickPhotoWorker(functionManager.container, builder)
}
}
这里生成Worker的最终实现是生成了一个 PickPhotoWorker,里面完成了选图的最终实现, 所以现在设计我们的Worker类:
先设计接口抽象行为:
interfaceWorker< Builder, ResultData> {
funstart( callBack: CoCoCallBack< ResultData>)
}
再设计一个抽象类,这一层去持有容器引用(比如我们需要启动Activity去选图、也需要感知容器生命周期) 和 我们构建好的Builder参数:
abstractclassBaseWorker< Builder, ResultData> ( valiContainer: IContainer, valmParams: Builder) :
Worker<Builder, ResultData>
再看看这个选图的最终PickPhotoWorker:
classPickPhotoWorker(iContainer: IContainer, builder: PickBuilder) :
BaseWorker<PickBuilder, PickResult>(iContainer, builder) {
overridefunstart(
callBack: CoCoCallBack< PickResult>
) {
valactivity = iContainer.provideActivity
activity ?: return
pickPhoto(activity, callBack)
}
privatefunpickPhoto(activity: Activity, callBack: CoCoCallBack< PickResult>) {
valpickIntent = 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)
}
}
privatefunhandleResult(
resultCode: Int,
intentData: Intent?,
callBack: CoCoCallBack< PickResult>
) {
if( null!= intentData && null!= intentData. data) {
valresult = PickResult
result.originUri = intentData. data!!
callBack.onSuccess(result)
} else{
callBack.onFailed(BaseException( “null result intentData”))
}
}
}
再看看容器的接口 iContainer:
interfaceIContainer{
//提供activity 判断状态、或者一些需要Context特有方法的场景
funprovideActivity: Activity?
// startActivityResult 启动activty、拿到回调结果
funstartActivityResult(intent: Intent, requestCode: Int,
callback: ( requestCode: Int, resultCode: Int, data: Intent?) -> Unit
)
//获得外部容器宿主
fungetLifecycleHost: Host
}
再看看它的具体实现当然也就是我们实际用于承载一切的的Fragment:
classAcceptResultSupportFragment: Fragment, IContainer,
Host {
/**
* 当前生命周期状态
*/
privatevarcurrent = Host.Status.INIT
privatevarrequestCode: Int= 0
privatevarcallback: ((requestCode: Int, resultCode: Int, data: Intent?) -> Unit)? = null
overridefunstartActivityResult(
intent: Intent, requestCode: Int,
callback: ( requestCode: Int, resultCode: Int, data: Intent?) -> Unit
) {
this.requestCode = requestCode
this.callback = callback
startActivityForResult(intent, requestCode)
}
overridefunprovideActivity: Activity? = activity
overridefunonActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
callback!!(requestCode, resultCode, data)
}
overridefunonActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//当外部想感知容器是否销毁当时候,读取这个变量即可
current = Host.Status.LIVE
}
overridefunonDestroy{
current = Host.Status.DEAD
super.onDestroy
callback = null
}
overridefungetStatus: Int{
returncurrent
}
overridefungetLifecycleHost: Host {
returnthis
}
}
而这个Fragment就是在我们第一步CoCo.with 创建FunctionManager 的时候创建的。
到这里,我们最开始调用的每一个操作符内部的基本链路就分析完成了、总结一下就是:
1. 根据当前是在Activity或者Fragment的状态创建一个功能提供器FunctionManager;
2. FunctionManager 选择指定功能之后产生相应功能对于的Builder 用于拼装各种参数
3. Builder参数拼装完成后调用父类公共方法 start 去生成相应功能对应的Worker,每个Worker去做自己的实现
4. Worker 做完成事情后回调给CallBack 拿到我们的Result
所以,以上述框架思想下,我拓展出了,系统拍照、系统选图、系统裁剪、文件异步处理(压缩、旋转、等图片操作)等总共四个方法,我们只需要一行代码即可完成,比如图片处理:
CoCo.with( this)
.dispose
.origin(imageFile.path)
.start( object: CoCoAdapter<DisposeResult> {
overridefunonSuccess( 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> {
overridefunonSuccess( data: DisposeResult) {
iv_image.setImageBitmap( data.compressBitmap)
}
})
效果图

设计Then 操作符:
按照之前的设计,我们在调用 start的方法的时候,直接生成了其对应的Worker开始执行,那现在如果我们想串联多个功能是不是我只需要在切换其他功能的操作符的时候,直接生成Woker对象存下来,最后完成所有的功能之后再依次按顺序调用start、将上一个Woker处理的结果丢给下一个Woker即可完成功能的串联?
设计流程:
1. 在FunctionManager中增加一个List用于保存所有要执行的Worker:
classFunctionManager( internalvalcontainer: IContainer) {
internalvalworkerFlows = ArrayList<Any>
}
2. 然后在原有BaseWorker 的位置添加then方法,当调用then 方法的时候生成Worker并添加到list中,然后返回 FunctionManager对象,保证我们完成一个工作之后,可以切换到FunctionManager的其他功能:
funthen: FunctionManager {
this.functionManager.workerFlows.add(generateWorker(getParamsBuilder))
returnthis.functionManager
}
3. 这个时候我们修改Worker的 start 方法,增加一个参数用于承接上一个Worker的结果:
interfaceWorker< Builder, ResultData> {
funstart(formerResult: Any?, callBack: CoCoCallBack< ResultData>)
}
4. 此时我们最终BaseFunctionBuilder的star方法的实现当然就是一个递归调用了:
abstractclassBaseFunctionBuilder< Builder, Result> (
internalvalfunctionManager: FunctionManager
) {
funthen: FunctionManager {
…
}
/**
* call start to begin the workflow
*/
funstart(callback: CoCoCallBack< Result>) {
this.functionManager.workerFlows.add(generateWorker(getParamsBuilder))
//循环取每一个Worker
valiterator = functionManager.workerFlows.iterator
if(!iterator.hasNext) {
return
}
//第一次开始,第一个Worker formerResult 为null
realApply( null, iterator, callback)
}
privatefunrealApply(
formerResult: Any?,
iterator: MutableIterator< Any>,
callback: CoCoCallBack< Result>
) {
valworker: Worker<Builder, Result> = iterator.next asWorker<Builder, Result>
worker.start(formerResult, object: CoCoCallBack<Result> {
overridefunonSuccess( data: Result) {
if(iterator.hasNext) {
iterator.remove
//如果还有下个,递归继续
realApply( data, iterator, callback)
} else{
//没有下个,结束流程、回调给最终结果
callback.onSuccess( data)
}
}
overridefunonFailed(exception: Exception) {
callback.onFailed(exception)
}
})
}
}
具体每个子类实现就不细贴了,大家有兴趣可以看看源码,就是将上个处理的结果串联起来,最终将Result一层一层传到最终的Result中,至此我们就完成了一行代码完成裁剪、拍照、处理、以及它们功能的任意组合的同时、还保证了代码的简洁性、可读性、易于拓展性。
5
总结
完整UML

CoCo 借鉴了Glide的语法和设计思想对系统常用功能如拍照、选图、裁剪 做了一些封装、不仅可以在处理图片等异步操作时候感知外部容器,而且通过类似于RxJava工作流的思想,这几个操作可以串联流式处理,大大提高了功能的拓展性和灵活性的同时,还能保证代码的简洁可读性,可以为我们日常开发减少很多臃肿代码,提高了开发效率,目前CoCo稳定版上线已经支持了多个应用稳定性运行。
GitHub地址:
https://github.com/soulqw/CoCo
责任编辑: