利用RxJava和Fragment解决Android中的割裂式编程

问题的产生

学会使用 Rxjava 后我们可以把之前用 listener 传回调的编程方式改为链式事件流编程方式,例如下面对第三方传 listener 的异步调用包装成一个返回 Observable 对象的函数:

public static Observable<List<Tip>> query(Context context, String querytext) {
    return Observable.create(subscriber -> {
        final Inputtips.InputtipsListener[] listener = {null};
        listener[0] = (list, i) -> {
            if (!subscriber.isUnsubscribed() &amp;&amp; i == RESULT_SUCCESS_CODE) {
                subscriber.onNext(list);
            }
            if (subscriber.isUnsubscribed()) {
                listener[0] = null;
            }
        };
        Inputtips inputtips = new Inputtips(context, new InputtipsQuery(querytext, null));
        inputtips.setInputtipsListener(listener[0]);
        inputtips.requestInputtipsAsyn();
    });
}

但是有些异步的调用是不提供让我们传Callback的形式的,例如我们在开发中经常会遇到这样的场景:

打开手机相册,调用相机拍照,申请系统权限

这类事件的回调通常由系统调用我们 Activity 或者 Fragment 的一些方法来运作的,如:

  onActivityResult onRequestPermissionResult 

像这类异步调用我们用不了上面的方式来包装成一个Observable对象,从而没法实现链式编程方式,我们在某处执行了某个动作但需要在其它的地方来处理异步的回调,这种割裂式的编程在项目业务代码复杂后在维护性和易读性上都大打折扣,那么我们应该怎么去解决这个问题呢?

我们发现这类问题的关键是在拿不到回调事件的事件源,回调事件的事件源在系统这些类已经定义好的回调方法 onActivityResult onRequestPermissionResult上, 而如果在这些方法中获取事件源就必然也是一种割裂式编程,如下面这种写法

private PublishSubject<ActivityResult> publishSubject = PublishSubject.create();
private Observable<ActivityResult> startActivity(){
    startActivityForResult(new Intent(),1);
    return publishSubject;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    ActivityResult activityResult = new ActivityResult();
    activityResult.setRequestCode(requestCode);
    activityResult.setResultCode(resultCode);
    activityResult.setData(data);
    publishSubject.onNext(activityResult);
}

这种写法虽然在某种意义上实现了事件流链式编程的方式,但是有一个缺点就是需要去改变现有代码,需要在我们的 BaseActivity 中去定义对应的 Subject 并在对应的方法中去发送事件,而且如果我们不是在Activity类中调用 startActivity而是在 Presenter或者ViewModel中调用,那这种依赖于 Activity 引用的侵入性的写法就不太好了。

解决思路

问题还是在于怎样获取到整个事件回调时的事件源,并将此回调事件和发起的调用事件串起来构成一条完整的事件流。
看了 RxPermissions 开源项目的源码后 我发现了一种思路:

利用一个无页面的 Fragment 去中转其所在 Activity 的一些生命周期回调事件

其中就包括onRequestPermissionResult事件这里需要注意的是有哪些fragment中的回调方法会同步Activity的事件,有没有什么限制,比如 : 如果要回调 fragmentonActivityResult 就必须是在该 fragment 中调用 startActivity 且所在 Activity 必须调用super.onActivityResult() 方法,如果项目中有人重写了 ActivityonActivityResult 方法,并且去掉了对 super.onActivityResult 的调用,那么 fragment 中的 onActivityResult 将接收不到回调事件(所以如果采用这种有限制的写法,就需要在团队中声明这种限制条件,以免出现问题)。这里有一篇关于无界面的 fragment 一些用法的文章:
用组合代替继承能为 Activity 带来什么

这样我们就解决了这个问题,我们可以愉快的使用这种链式调用的方式了:例如这里的几个库已经实现了这种调用方式

RxPermissions
RxActivityResult(做了一些安全性的处理,是利用无页面的 Activity 做中转的)
RxActivityResult(利用无界面 Fragment 做事件中转)

RxPermissions:

rxPermissions
    .request(Manifest.permission.CAMERA)
    .subscribe(granted -> {
        if (granted) { // Always true pre-M
        // I can control the camera now
        } else {
        // Oups permission denied
        }
    });

RxActivityResult:

RxActivityResult.startActivityForResult(this, intent, REQUEST_CODE)
        .subscribe(new Consumer<ActivityResult>() {
            @Override
            public void accept(@NonNull ActivityResult result) throws Exception {
                if (result.isOk()) {
                    final Intent data = result.getData();
                    // DO SOME THING
                }
            }
        });

当然还有另一种通用的侵入式写法就是一开始提到的。 可以在 BaseActivityBaseFragment 里面创建 Subject 然后在对应的回调中去发射对应的事件,这种写法的优缺点也比较明显:必须在基类中做这些处理,对代码的侵入性比较大,但是兼用性较好,可以处理一切回调方法(不需要考虑 fragment 的事件同步问题),性能也最优(不需要创建一个中转 fragment 或者 activity

选择两种方式都是可以的 我个人倾向于选用第一种利用中间件( Fragment 或者Activity )的方式,主要是无侵入性,随到随用,需要使用时就调用提前包装好的方法即可,只要了解处理好生命周期的兼容性问题即可。

其实当我们获取到 Activity 或者 Fragment 中这些回调事件事件源 Observable 后我们可以有一些其他的用途,例如可以组合onDestoryOnDestoryViewObservable 到网络请求的事件中,这样就可以在页面销毁时及时停止掉网络请求避免一些内存泄漏和空指针异常,这个也有类似已经实现了的库也是上面的两种方式:
RxLifecycle
RxLifecycle(利用fragment承载事件)

最后

谢谢阅读! 谢谢RxJava带给我们这些好用的功能