Kotlin Coroutine 规避回调地狱
场景描述
- 假设有这样的一个业务场景
- 第0步:用户通过本地选择一张图片
- 第1步:因为这张图片会很大,不方便之后的HTTP协议或者应用内传输,我们需要压缩,记为PictureCompress操作
- 第2步:压缩完成之后,需要对这样图片进行一个抠图行为,过滤掉图片原有的一些无用信息,记为PictureMatting操作
- 第3步:在抠图行为完成之后,需要对抠图保存下来的这张图片进行一个加入滤镜的操作,记为PictureFilter操作
- 最终,将该步骤的完整体显示给到用户
- 因为每一个步骤中有很多情况,比如第1步就对应图片压缩成功或者失败两种情况,第2步和第3步也是同理。
- 其流程图,如下所示:
重要说明:
- 以下代码,均为伪代码,只提供大致思路。
- 具体应用在业务中,需要严格遵守单一数据源,以及MVVM的分层职责。
使用Java回调方式
首先,需要定义各个状态模型,如下是压缩的状态模型,实际还要将抠图、滤镜的状态模型全部描述出来
java
public static abstract class CompressImageUIState {
public static class SuccessUIState extends CompressImageUIState {
// 里面需要使用具体的变量去保存所需要的内容
// 比如存个Bitmap,然后在构造方法里面去依赖注入,然后通过Get方法去取出来
@NotNull
private final Bitmap afterCompressBitmap;
}
public static class FailUIState extends CompressImageUIState {
// 里面需要使用具体的变量去保存所需要的异常行为
// 比如存一个Throwable
@NotNull
private final Throwable throwable;
}
}
java
// SAM接口
public interface Consumer<T> {
void accept(T data);
}
public interface ImageOperation {
void compressImage(@NotNull Bitmap bitmap, Consumer<PicutureCompressImageUIState> consumer);
void mattingImage(@NotNull Bitmap bitmap, Consumer<PicutureMattingImageUIState> consumer);
void filterImage(@NotNull Bitmap bitmap, Consumer<PictureFilterImageUIState> consumer);
}
java
// 主流程
final Bitmap originBitmap = new Bitmap();
// 压缩
compressImage(originBitmap, compressImage -> {
if (compressImage instanceof PicutureCompressImageUIState.SuccessUIState) {
// 压缩成功,进入抠图
final Bitmap afterCompressBitmap = ((PicutureCompressImageUIState.SuccessUIState) compressImageUIState).afterCompressBitmap;
mattingImage(afterCompressBitmap, mattingImageUIState -> {
if (mattingImageUIState instanceof PicutureMattingImageUIState.SuccessUIState) {
// 抠图成功,进入滤镜加入环节
final Bitmap mattingBitmap = ((PictureMattingImageUIState.SuccessUIState) mattingImageUIState).afterMattingBitmap;
filterImage(mattingBitmap, pictureFilterUIState -> {
if (pictureFilterUIState instanceof PictureFilterImageUIState.SuccessUIState) {
// 显示图片
final pictureFilterBitmap = ((PictureFilterImageUIState.SuccessUIState) pictureFilterUIState).afterPictureFilterBitmap;
pictureFilterBitmap.show()
} else {
// 弹出加入滤镜失败Toast
Toast.makeText(this, "加入滤镜失败").show()
}
});
} else if (pictureMattingImageUIState instanceof PicutureMattingImageUIState.FailedUIState) {
// 抠图失败,Toast
Toast.makeText(this, "抠图失败").show();
}
});
} else if (compressImage instanceof PicutureCompressImageUIState.FailedUIState) {
// 压缩失败,Toast
Toast.makeText(this, "压缩图片失败").show();
}
});
- 以上是Java中使用Callback的形式,处理一些异步行为的常规做法。
- 当这个流程的环节变得更加多,需要异步处理的动作更多,那么这个callback导致的嵌套可能更大,也就是陷入我们常说的回调地狱之中。
使用RxJava
java
public interface ImageOperation {
/**
* - 成功,返回压缩后的Bitmap
* - 失败:扔出异常,自定义为PictureCompressException
*/
Single<Bitmap> compressImage(@NotNull Bitmap bitmap);
/**
* - 成功,返回压缩后的Bitmap
* - 失败:扔出异常,自定义为PictureMattingException
*/
Single<Bitmap> mattingImage(@NotNull Bitmap bitmap);
/**
* - 成功,返回压缩后的Bitmap
* - 失败:扔出异常,自定义为PictureFilterException
*/
Single<Bitmap> filterImage(@NotNull Bitmap bitmap);
}
java
final var originBitmap = new Bitmap();
imageOperation
// 压缩行为
.compressImage(originBitmap)
// 抠图行为
.flatMap(compressBitmap -> mattingImage(it))
// 加入滤镜行为
.flatMap(mattingBitmap -> filterImage(it))
// 线程调度行为
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.main())
// 订阅行为
.subscribe(new SingleObserver<>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
mCompositeDisposable.add(disposable);
}
@Override
public void onSuccess(@NotNull Bitmap bitmap) {
bitmap.show();
}
@Override
public void onError(@NotNull Throwable e) {
if (e instanceof PictureCompressException) {
// 压缩失败,Toast
Toast.makeText(this, "压缩图片失败").show();
} else if (e instanceof PictureMattingException) {
// 抠图失败,Toast
Toast.makeText(this, "抠图失败").show();
} else if (e instanceof PictureFilterException) {
// 加入滤镜失败,Toast
Toast.makeText(this, "加入滤镜失败").show()
}
}
})
使用Kotlin Coroutine
- 去使用同步的方式写异步代码
kotlin
interface ImageOperation {
/**
* - 成功,返回压缩后的Bitmap
* - 失败:扔出异常,自定义为PictureCompressException
*/
suspend fun compressImage(bitmap: Bitmap): Bitmap
/**
* - 成功,返回抠图后的Bitmap
* - 失败:扔出异常,自定义为PictureMattingException
*/
suspend fun mattingImage(bitmap: Bitmap): Bitmap
/**
* - 成功,返回过滤后的Bitmap
* - 失败:扔出异常,自定义为PictureFilterException
*/
suspend fun filterImage(bitmap: Bitmap): Bitmap
}
kotlin
// 主流程
val originBitmap = Bitmap()
scope.launch {
val compressImageBitmap = try {
// 压缩成功,返回压缩图片
compressImage(originBitmap)
} catch (e: PictureCompressException) {
// 压缩失败,Toast
Toast.makeText(this, "压缩图片失败").show()
return
}
val mattingImageBitmap = try {
mattingImage(compressImageBitmap)
} catch (e: PictureMattingException) {
// 抠图失败,Toast
Toast.makeText(this, "抠图失败").show()
return
}
val filterImageBitmap = try {
filterImage(mattingImageBitmap)
} catch (e: PictureFilterException) {
// 加入滤镜失败,Toast
Toast.makeText(this, "加入滤镜失败").show()
return
}
filterImageBitmap.show()
}
- 另外一种处理异常的问题
kotlin
// 主流程
val originBitmap = Bitmap()
scope.launch(CoroutineExceptionHandler { _, throwable -> {
when (throwable) {
is PictureCompressException -> {
// 压缩失败,Toast
Toast.makeText(this, "压缩图片失败").show();
}
is PictureMattingException -> {
// 抠图失败,Toast
Toast.makeText(this, "抠图失败").show();
}
is PictureFilterException -> {
// 加入滤镜失败,Toast
Toast.makeText(this, "加入滤镜失败").show()
}
}
}) {
val compressImageBitmap = compressImage(originBitmap)
val mattingImageBitmap = mattingImage(compressImageBitmap)
val filterImageBitmap = filterImage(mattingImageBitmap)
filterImageBitmap.show()
}
- 结合Kotlin的拓展函数,建立一个构造器模式
kotlin
private suspend fun Bitmap.compressImageBitmap(): Bitmap {
return service.compressImage(this)
}
private suspend fun Bitmap.mattingImageBitmap(): Bitmap {
return service.mattingImage(this)
}
private suspend fun Bitmap.filterImageBitmap(): Bitmap {
return service.filterImage(this)
}
// 主流程可以写成这样
val originBitmap = Bitmap()
scope.launch(CoroutineExceptionHandler { _, throwable -> {
when (throwable) {
is PictureCompressException -> {
// 压缩失败,Toast
Toast.makeText(this, "压缩图片失败").show();
}
is PictureMattingException -> {
// 抠图失败,Toast
Toast.makeText(this, "抠图失败").show();
}
is PictureFilterException -> {
// 加入滤镜失败,Toast
Toast.makeText(this, "加入滤镜失败").show()
}
}
}) {
// 一个很完美的链式调用如下所示:
originBitmap.compressImageBitmap()
.mattingImageBitmap()
.filterImageBitmap()
.show()
}