利用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() && 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
的事件,有没有什么限制,比如 : 如果要回调 fragment
中onActivityResult
就必须是在该 fragment
中调用 startActivity
且所在 Activity
必须调用super.onActivityResult()
方法,如果项目中有人重写了 Activity
的 onActivityResult
方法,并且去掉了对 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
}
}
});
当然还有另一种通用的侵入式写法就是一开始提到的。 可以在 BaseActivity
和BaseFragment
里面创建 Subject
然后在对应的回调中去发射对应的事件,这种写法的优缺点也比较明显:必须在基类中做这些处理,对代码的侵入性比较大,但是兼用性较好,可以处理一切回调方法(不需要考虑 fragment
的事件同步问题),性能也最优(不需要创建一个中转 fragment
或者 activity
)
选择两种方式都是可以的 我个人倾向于选用第一种利用中间件( Fragment
或者Activity
)的方式,主要是无侵入性,随到随用,需要使用时就调用提前包装好的方法即可,只要了解处理好生命周期的兼容性问题即可。
其实当我们获取到 Activity
或者 Fragment
中这些回调事件事件源 Observable
后我们可以有一些其他的用途,例如可以组合onDestory
和OnDestoryView
的 Observable
到网络请求的事件中,这样就可以在页面销毁时及时停止掉网络请求避免一些内存泄漏和空指针异常,这个也有类似已经实现了的库也是上面的两种方式:
RxLifecycle
RxLifecycle(利用fragment承载事件)
最后
谢谢阅读! 谢谢RxJava带给我们这些好用的功能