MVP架构下解决 RxJava 自动解绑问题
背景
MVP 模式下使用 RxJava 处理网络访问的回调,当数据返回时 Presenter 调用绑定的 View 的方法。
定义 BasePresenter 如下:
public class BasePresenter<T extends MvpView> implements Presenter<T> {
private T mMvpView;
@Override
public void attachView(T mvpView) {
mMvpView = mvpView;
}
@Override
public void detachView() {
mMvpView = null;
}
public boolean isViewAttached() {
return mMvpView != null;
}
public T getMvpView() {
return mMvpView;
}
}
定义 MvpView 如下:
public interface MvpView {
}
举一个具体的实现,有记录页为 RecordActivity,定义如下:
public class RecordRecordActivity extends BaseActivity
implements RecordMvpView {
@Inject
RecordPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
presenter.attachView(this);
//...
onRefresh();
}
public void onRefresh(){
presenter.queryReocrd();
}
@Override
protected void onDestroy() {
presenter.detachView();
super.onDestroy();
}
@Override
public void showRecord(List<RecordResponse> data) {
//...
}
}
RecordMvpView 定义如下:
public interface RecordMvpView extends MvpView {
void showRecord(List<RecordResponse> data);
}
RecordPresenter 的定义如下:
public class RecordPresenter extends BasePresenter<RecordMvpView> {
private DataManager dataManager;
private Disposable disposable;
@Inject
public RecordPresenter(DataManager dataManager) {
this.dataManager = dataManager;
}
public void queryRecord(int page, int count) {
RxUtil.dispose(disposable);
dataManager.queryRecord(page, count)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<List<RecordResponse>>() {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onSuccess(List<RecordResponse> resp) {
getMvpView().showRecord(resp);
}
@Override
public void onError(Throwable e) {
Timber.e(e);
getMvpView().showRecord(null);
}
});
}
}
以上实现具有一个重大问题,当 Activity 处于 destroy 状态时调用 onRefresh 方法去加载数据,会导致 Presenter 中处理数据返回后调用 getMvpView 方法返回 null,从而导致 NPE。抑或,在 create 状态请求的数据在未返回前 Activity 就进入了 destroy 状态从而导致 NPE。
解决方案
(1)最简单的也是最复杂的解决方案
在调用 getMvpView 前进行判空,即:
if(isViewAttached()){
getMvpView().showRecord(resp);
}
是不是很简单?是的,看起来简单,其实是最复杂的,应该它需要在每个回调的地方小心翼翼地包上这层判断,工作量大还容易出错,代码也不好看。更关键的是,它到底是执行了,并没有在 View 销毁后立即停止订阅。
(2)使用第三方库 RxLifecycle 或 AutoDispose
这两个都是较出名的用于解决 RxJava 与Android 生命周期问题的第三方库。可以自行 Github 一下。
RxLifecycle
This library allows one to automatically complete sequences based on a second lifecycle stream.
This capability is useful in Android, where incomplete subscriptions can cause memory leaks.
该库允许在接收到第二个生命周期流时自动结束订阅。
此种能力对于解决因未完成的订阅导致的Android 内存泄漏问题很有用。
RxLifecycle 的局限性:
需要继承自 RxActivity 或 RxFragment 等;
其核心步骤需要 RxActivity 或 RxFragment 的引用。
.compose(this.<T>bindUntilEvent(ActivityEvent.PAUSE))
AutoDispose
AutoDispose is an RxJava 2 tool for automatically binding the execution of RxJava 2 streams to a provided scope via disposal/cancellation.
AutoDispose 是 RxJava2 中的一款工具,能通过解绑或取消操作,使得 RxJava2 流执行到在给定的域中为止。
其受 RxLifecycle 启发。
优势:
- 将生命周期相关的从 Activity 或 Fragment 中分离出来,独立成 LifecycleOwner,可扩展。
(3)结合项目情况自定义解决方案
RxLifecycle 的原理是:
- BehaviorSubject 在订阅后会发送前一个数据值;
- ObservableTransformer、SingleTransformer 等等可以对整个流进行操作;
- takeUntil 操作符可以在第二个被观察者发送事件时自动停止订阅。
结合 MVP 架构和 RxLifecycle 的原理,我的解决方案是:
- 使用 BehaviorSubject 发送 View 的绑定情况;
- 在所有跟 View 相关的流中使用 compose 操作符,compose 一个自定义的 LifecycleTransformer 操作整个流,使用 takeUntil 操作符在观察到 BehaviorSubject 发送解绑消息后使用停止订阅。
具体代码实现如下:
BasePresenter 修改为:
public class BasePresenter<T extends MvpView> implements Presenter<T> {
private T mMvpView;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private BehaviorSubject<Boolean> behaviorSubject = BehaviorSubject.createDefault(false);
@Override
public void attachView(T mvpView) {
mMvpView = mvpView;
behaviorSubject.subscribe();
behaviorSubject.onNext(true); // TRUE 表示 View 绑定了
}
@Override
public void detachView() {
mMvpView = null;
behaviorSubject.onNext(false); // FALSE 表示 View 解绑了
compositeDisposable.clear();
}
public void addDisposable(Disposable... disposables) {
for (Disposable d : disposables) {
compositeDisposable.add(d);
}
}
protected void deleteDispoable(Disposable disposable) {
compositeDisposable.delete(disposable);
}
protected <R> LifecycleTransformer<R> bindLifeCycle() {
return new LifecycleTransformer<>(behaviorSubject, this);
}
public boolean isViewAttached() {
return mMvpView != null;
}
public T getMvpView() {
return mMvpView;
}
}
其中 LifecycleTransformer 定义为:
public final class LifecycleTransformer<T>
implements ObservableTransformer<T, T>, SingleTransformer<T, T>, MaybeTransformer<T, T>, CompletableTransformer {
private final Observable<Boolean> observable;
private BasePresenter presenter;
public LifecycleTransformer(Observable<Boolean> observable, BasePresenter presenter) {
this.observable = observable;
this.presenter = presenter;
}
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.takeUntil(getFilterFalseObservable()) //当收到 View 解绑消息时自动解除订阅
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
if (!presenter.isViewAttached()) { // 当订阅时,若 View 解绑则自动解除订阅
Timber.v("dispose");
disposable.dispose();
return;
}
presenter.addDisposable(disposable);
});
}
@Override
public SingleSource<T> apply(Single<T> upstream) {
return upstream.takeUntil(getFilterFalseObservable().firstOrError())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
if (!presenter.isViewAttached()) {
Timber.v("dispose");
disposable.dispose();
return;
}
presenter.addDisposable(disposable);
});
}
@Override
public CompletableSource apply(Completable upstream) {
return Completable.ambArray(upstream,
getFilterFalseObservable().flatMapCompletable(CANCEL_COMPLETABLE))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
if (!presenter.isViewAttached()) {
Timber.v("dispose");
disposable.dispose();
return;
}
presenter.addDisposable(disposable);
});
}
@Override
public MaybeSource<T> apply(Maybe<T> upstream) {
return upstream.takeUntil(getFilterFalseObservable().firstElement())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
if (!presenter.isViewAttached()) {
Timber.v("dispose");
disposable.dispose();
return;
}
presenter.addDisposable(disposable);
});
}
private Observable<Boolean> getFilterFalseObservable() {
return observable.filter(aBoolean -> !aBoolean);
}
private static final Function<Object, Completable> CANCEL_COMPLETABLE =
ignore -> Completable.error(new CancellationException());
}
这样原先的 RecordPresenter 就可以修改为:
public class RecordPresenter extends BasePresenter<RecordMvpView> {
private DataManager dataManager;
@Inject
public RecordPresenter(DataManager dataManager) {
this.dataManager = dataManager;
}
public void queryRecord(int page, int count) {
dataManager.queryRecord(page, count)
.compose(bindLifeCycle()) // 关键代码
.subscribe(new LifecycleSingleObserver<List<RecordResponse>>() {
@Override
public void onSuccess(List<RecordResponse> resp) {
Timber.i("onSuccess --------------");
getMvpView().showRecord(resp);
}
@Override
public void onError(Throwable e) {
Timber.i(e, "onError --------------");
getMvpView().showRecord(null);
}
});
}
}
其中 LifecycleSingleObserver 是为了简化 SingleObserver 引入的,定义如下:
public abstract class LifecycleSingleObserver<T> implements SingleObserver<T> {
@Override
public void onSubscribe(Disposable d) {
}
}
至此,关于订阅的绑定及生命周期的问题已经在基类进行解决,具体使用时的代码大大简化(至少少了 5 行代码),只需加上一句 .compose(bindLifeCycle())
即可。
MVP架构下解决 RxJava 自动解绑问题的更多相关文章
- [Android] Android MVP 架构下 最简单的 代码实现
Android MVP 架构下 最简单的 代码实现 首先看图: 上图是MVP,下图是MVC MVP和MVC的区别,在于以前的View层不仅要和model层交互,还要和controller层交互.而 ...
- jquery移除元素时会自动解绑事件
.html() When .html() is used to set an element's content, any content that was in that element is co ...
- AutoDispose代替RxLifecycle优雅的解决RxJava内存泄漏问题
使用过Rxjava的小伙伴都知道,在使用RxJava时如果处理不当,很可能会产生内存泄漏的问题. 我们使用rxjava最大的原因是响应式编程使我们的异步操作代码变得很优雅,在Android中,也使线程 ...
- jQuery 学习笔记(5)(事件绑定与解绑、事件冒泡与事件默认行为、事件的自动触发、自定义事件、事件命名空间、事件委托、移入移出事件)
1.事件绑定: .eventName(fn) //编码效率略高,但部分事件jQuery没有实现 .on(eventName, fn) //编码效率略低,所有事件均可以添加 注意点:可以同时添加多个相同 ...
- ARM架构下的Docker环境,OpenJDK官方没有8版本镜像,如何完美解决?
为什么需要ARM架构下的OpenJDK8的Docker镜像? 对现有的Java应用,之前一直运行在x86处理器环境下,编译和运行都是JDK8,如今在树莓派的Docker环境运行(或者其他ARM架构电脑 ...
- Ubuntu下解决解压zip文件中文文件名乱码问题
在Ubuntu下解压Windows下压缩的zip文件时,会出现解压出的带中文文件名的文件名乱码,这是因为Ubuntu和Windows默认的编码不同,Ubuntu下默认的编码是UTF-8,而Window ...
- Tomcat 没有自动解压webapp下的war项目文件问题
默认选择的tomcat安装在了C盘下的C:\Program Files下 所以webapp文件也在C盘下 选择启动tomcat时 我选择了 bin下的 Tomcat.exe 显示成功启动 打开项目网站 ...
- 如何解决aws解绑银行卡问题?
首先先来说明一下我自己的情况? 一年的免费使用 前提:没有开启任何的实例服务 先贴一条官方的解释 关于我小白一个.学校课程要求使用aws,注册之后在网络上看到一堆人踩坑,aws的扣费就是个坑! 预授权 ...
- 解决Linux下SSH超时自动断开
title: 解决Linux下SSH超时自动断开 comments: false date: 2019-08-19 19:22:55 description: Linux 下 SSH 超时自动断开?? ...
随机推荐
- 用链表和数组实现HASH表,几种碰撞冲突解决方法
Hash算法中要解决一个碰撞冲突的办法,后文中描述了几种解决方法.下面代码中用的是链式地址法,就是用链表和数组实现HASH表. he/*hash table max size*/ #define HA ...
- 走近Java之包装器类Integer
前几天,有个同事问了我一个关于Integer类赋值的问题,很有意思,我们一起来看一下(如果有说的不正确的地方,欢迎大家指正). 如上图,同样是赋值,但是两次比较的结果完全不同.我们走近了解一下. 在I ...
- .NET Core 学习资料精选:入门
开源跨平台的.NET Core,还没上车的赶紧的,来不及解释了-- 本系列文章,主要分享一些.NET Core比较优秀的社区资料和微软官方资料.我进行了知识点归类,让大家可以更清晰的学习.NET Co ...
- C程序中可怕的野指针
一.疑问点指针是C语言一个很强大的功能,同时也是很容易让人犯错的一个功能,用错了指针,轻者只是报个错,重者可能整个系统都崩溃了.下面是大家在编写C程序时,经常遇到的一种错误的使用方法,也许在你的学习和 ...
- spark 源码分析之十六 -- Spark内存存储剖析
上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...
- 【朝花夕拾】Android自定义View篇之(十一)View的滑动,弹性滑动与自定义PagerView
前言 由于手机屏幕尺寸有限,但是又经常需要在屏幕中显示大量的内容,这就使得必须有部分内容显示,部分内容隐藏.这就需要用一个Android中很重要的概念——滑动.滑动,顾名思义就是view从一个地方移动 ...
- Android studio 3.4.1 使用 bootstrap 中的组件实例
电脑环境: ubuntu18.04 + Android studio 3.4.1 + bootsrtap4 Android studio中板式设计主要使用的 XML 布局文件,而在bootstrap中 ...
- 洛谷P3877 [TJOI2010]打扫房间 解题报告
首先整理一下条件: 1.恰好进出每个需打扫的房间各一次 2.进出每个房间不能通过同一个门 (其实前两个条件是一回事) 3.要求每条路线都是一个闭合的环线 4.每条路线经过的房间数大于2 让你在一个n* ...
- 前端html+css+JavaScript 需要掌握的单词
前端html+css+JavaScript 需要掌握的单词 broswer 浏览器(客户端) html 超文本标记语言 css 层叠样式表 javascript 语言名字(类似python/php ...
- UVA663 Sorting Slides(烦人的幻灯片)
UVA663 Sorting Slides(烦人的幻灯片) 第一次做到这么玄学的题,在<信息学奥赛一本通>拓扑排序一章找到这个习题(却发现标程都是错的),结果用二分图匹配做了出来 蒟蒻感觉 ...