Skip to content

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()
}

随便写写的,喜欢就好。 使用VitePress构建