/ 简介 /
熟悉 RxJava 的同学, 当我们开启一个异步任务时, 通常需要在 Activity/Fragment 销毁时, 及时关闭异步任务, 否则就会有内存泄漏的.
一般的做法是订阅成功后, 拿到 Disposable 对象, 在 Activity/Fragment 销毁时, 调用 Disposable 对象的 dispose() 方法, 将异步任务中断, 也就是中断 RxJava 的管道, 代码如下:
- Disposable disposable = Observable
- .interval(0, 1, TimeUnit.SECONDS) // 开启一个定时器
- .subscribe(aLong -> {
- });
- //Activity/Fragment 销毁时, 中断 RxJava 管道
- if (disposable != null && !disposable.isDisposed()) {
- disposable.dispose();
- }
这种做法在代码的执行效率上是最高效, 性能最优的, 然而这种做法在开发效率上却是最低的.
试想, 如果我们开启了 n 个异步任务, 就需要在 Activity/Fragment 销毁时中断 n 个异步任务. 对于这种写法, 身患强迫症的我, 实在不能接受. 也许你们会说, 可以使用 CompositeDisposable 类, 就可以避免手写关闭 n 个异步任务的代码, 只需要关闭一次即可. 没毛病, 确实可以, 然而这种做法也仅仅是避免了我们手写关闭异步任务的代码而已. 追求极致的我, 也不能接受这种写法, 此时我就想, 能不能就用一行代码解决这个问题呢? 于是乎, 就开启了我的探索之路, 于是乎, 就有了 RxLife.
先来介绍下 RxLife, 相较于 trello/RxLifecycle,uber/AutoDispose, 具有如下优势:
直接支持在主线程回调
支持在子线程订阅观察者
简单易用, 学习成本低
性能更优, 在实现上更加简单
gradle 依赖
- dependencies {
- implementation 'com.rxjava.rxlife:rxlife:1.0.8'
- //if you use AndroidX
- implementation 'com.rxjava.rxlife:rxlife-x:1.0.8'
- }
- / RxLife 使用 /
- Activity/Fragment
首先, 我们来看看在 Activity/Fragment 上如何使用, 如下:
- // 在 Activity/Fragment 上
- Observable.interval(1, 1, TimeUnit.SECONDS)
- .as(RxLife.as(this)) // 这里 this 为 LifecycleOwner 接口对象
- .subscribe(aLong -> {
- Log.e("LJX", "onNext aLong=" + aLong);
- });
没错, 就是这么简单粗暴, 在这, 我们只需要将 RxLife.as(this) 传入 RxJava 的 as 操作符即可. 此时当 Activity/Fragment 销毁, 就会自动关闭 RxJava 管道, 避免内存泄漏.
View
接着来看看在 View 上如何使用, 如下:
- // 在 View 上
- Observable.interval(1, 1, TimeUnit.SECONDS) // 隔一秒发送一条消息
- .as(RxLife.as(this)) // 这里 this 为 View 对象
- .subscribe(aLong -> {
- Log.e("LJX", "onNext aLong=" + aLong);
- });
咦? 这跟上面的代码不是一模一样的吗? 是的, 代码一模一样, 但是在这我们传入的 this 是一个 View 对象. 此时当 View 从窗口中移除时 (执行了 onDetachedFromWindow 方法), 就会自动关闭 RxJava 管道, 避免内存泄漏.
ViewModel
ViewModel 是 Google Jetpack 里面的组件之一, 由于它能自动感知 Activity/Fragmeng 的销毁, 所以 RxLife 单独为它做了适配. 在 ViewModel 中使用 RxLife, 需要继承 RxLife 的 ScopeViewModel 类, 然后就可以跟上面一样, 优雅的使用 RxLife.as(this), 如下:
- public class MyViewModel extends ScopeViewModel {
- public MyViewModel() {
- Observable.interval(1, 1, TimeUnit.SECONDS)
- .as(RxLife.as(this)) // 这里的 this 为 Scope 接口对象
- .subscribe(aLong -> {
- Log.e("LJX", "onNext aLong=" + aLong);
- });
- }
- }
此时当 Activity/Fragmeng 销毁时, 就会自动关闭 RxJava 管道, 避免内存泄漏.
注意: 要想 ViewModel 对象感知 Activity/Fragment 销毁事件, 就不能使用 new 关键字创建对象, 必须要通过 ViewModelProviders 类获取 ViewModel 对象, 如下:
- // 在 Activity/Fragment 上
- MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)
任意类
相信大家对 MVP 都非常的熟悉了, 在 P 层, 我们一般都有发送 Http 请求的需求, 此时, 我们也希望, 在 Activity/Fragment 销毁时, 能自动将 Http 关闭, 所以 RxLife 对任意类做了点适配工作. 在任意类中, 我们需要继承 RxLife 的 BaseScope 类, 然后就优雅的使用 RxLife.as(this) 了, 如下:
- public class Presenter extends BaseScope {
- public Presenter(LifecycleOwner owner) {
- super(owner);
- Observable.interval(1, 1, TimeUnit.SECONDS)
- .as(RxLife.as(this)) // 这里的 this 为 Scope 接口对象
- .subscribe(aLong -> {
- Log.e("LJX", "onNext aLong=" + aLong);
- });
- }
- }
- kotlin
在上面的代码中, 我们使用了 as 操作符, 然后在 kotlin 中, as 是一个关键字, 使用起来就不是很方便, 所以 RxLife 对 kotlin 做了适配工作, 在 kotlin 中, 我们可以使用 life 替代 as 操作符, 并且更加的简洁, 如下:
- Observable.intervalRange(1, 100, 0, 200, TimeUnit.MILLISECONDS)
- .life(this)
- .subscribe { aLong ->
- Log.e("LJX", "onNext aLong=" + aLong)
- }
- / 原理 /
说起原理, 其实 trello/RxLifecycle,uber/AutoDispose,RxLife 三者的原理都是一样的, 都是拿到最低层观察者的 Disposable 对象, 然后在某个时机, 调用该对象的 Disposable.dispose() 方法中断管道, 以达到目的. 原理都一样, 然而实现却大不相同.
trello/RxLifecycle (3.0.0 版本) 内部只有一个管道, 但却有两个事件源, 一个发送生命周期状态变化, 一个发送正常业务逻辑, 最终通过 takeUntil 操作符对事件进行过滤, 当监听到符合条件的事件时, 就会将管道中断, 从而到达目的.
uber/AutoDispose(1.2.0 版本) 内部维护了两个管道, 一个是发送生命周期状态变化的管道, 我们称之为 A 管道, 另一个是业务逻辑的管道, 我们称至为 B 管道, B 管道持有 A 管道的观察者引用, 故能监听 A 管道的事件, 当监听到符合条件的事件时, 就会将 A,B 管道同时中断, 从而到达目的.
RxHttp 内部只有一个业务逻辑的管道, 通过自定义观察者, 拿到 Disposable 对象, 暴露给 Scope 接口, Scope 的实现者就可以在合适的时机调用 Disposable.dispose() 方法中断管道, 从而到达目的.
RxLife 具体实现
光从文字层面上所原理, 好像有点抽象, 接下来, 我们看看 RxLife 在代码层面上是如何实现的. 在上面的代码案例中, 我们皆能看到 RxLife.as(this) 这行代码的身影, 那这个 as 方法接收的是什么类型的参数呢? 我们看看源码:
上面一共有 10 个 as 系列方法, 其中有 8 个是对外提供的. 而且前面 9 个方法最终都会调用第 10 个 as(Scope scope, boolean onMain) 方法.
我们先粗略来看几个方法
as(LifecycleOwnerowner owner) 方法, 接收的是一个 LifecycleOwner 接口对象, 简单介绍下这个接口, 这个接口对象能使我们自定义的类感知 Activity/Fragment 的生命周期回调. 我们常见的 Activity/Fragment 就实现了这个接口, 所以我们就能够在 Activity/Fragment 中调用此. 法.
as(View view) 这个方法就很直观了, 直接接收一个 View 对象, 我们在 View 上调用的就是这个方法.
as(Scope scope) 方法接收一个 Scope 接口对象, 后面会对这个接口介绍, 这里可以告诉大家的是, 在上面的 ViewModel 及任意类中继承的 ScopeViewModel,BaseScope 类都实现了 Scope 接口, 所以我们在 ViewModel 及任意类中调用的就是这个 as 方法.
Scope 接口
Scope, 翻译过来就是作用域的意思. 那么什么是作用域, 简单来说, 就是一个对象从创建到死亡, 这就是它的作用域, 比如: Activity/Fragment 的作用域就是从 onCreate 到 onDestroy;View 的作用域就是从 onAttachedToWindow 到 onDetachedFromWindow;ViewModel 的作用域就是从构造方法到 onCleared 方法; 其它任意类的作用域就是从创建到销毁, 当然, 你也可以自己指定一些类的作用域. 到这, 我们来看看 Scope 接口里面都有啥:
- public interface Scope {
- // 订阅事件时, 回调本方法, 即在 onSubscribe(Disposable d) 方法执行时回调本方法
- void onScopeStart(Disposable d);
- //onError/onComplete 时调回调此方法, 即事件正常结束时回调
- void onScopeEnd();
- }
此接口描述的就是 RxJava 的作用域, 即从事件订阅到事件结束. 到这, 也许有人已经知道了, 只要我们实现了这个接口, 就能拿到 Disposable 对象, 然后就可以在某个时刻, 中断 RxJava 短道, 提前结束 RxJava 作用域. 从而使得 RxJava 的作用域小于等于调用者的作用域, 避免了内存泄漏.
我们简单看一下 BaseScope 类的具体实现.
- public class BaseScope implements Scope, GenericLifecycleObserver {
- private CompositeDisposable mDisposables;
- public BaseScope(LifecycleOwner owner) {
- owner.getLifecycle().addObserver(this);
- }
- @Override
- public void onScopeStart(Disposable d) {
- addDisposable(d);
- }
- @Override
- public void onScopeEnd() {}
- private void addDisposable(Disposable disposable) {
- CompositeDisposable disposables = mDisposables;
- if (disposables == null) {
- disposables = mDisposables = new CompositeDisposable();
- }
- disposables.add(disposable);
- }
- private void dispose() {
- final CompositeDisposable disposables = mDisposables;
- if (disposables == null) return;
- disposables.dispose();
- }
- @Override
- public void onStateChanged(LifecycleOwner source, Event event) {
- //Activity/Fragment 生命周期回调
- if (event == Event.ON_DESTROY) { //Activity/Fragment 销毁
- source.getLifecycle().removeObserver(this);
- dispose(); // 中断 RxJava 管道
- }
- }
- }
可以看到, BaseScope 实现非常简单, 在 onScopeStart 方法中拿到 Disposable 对象添加进 CompositeDisposable 对象, 然后在 Activity/Fragment 销毁使, 调用 CompositeDisposable 对象的 dispose 方法, 统一中断 RxJava 管道, 从而达到目的.
Scope 一共有 4 个实现类, 分别是: LifecycleScope,ViewScope,ScopeViewModel 及 BaseScope,BaseScope 上面已经介绍, 其它 3 个原理都一样, 只是在实现上会有一点点不同, 这就不在一一介绍了.
/ 问题暴露 /
我们知道, 任意类想要监听 Activity/Fragment 生命周期回调, 都必须要实现 LifecycleObserver 接口, 然后通过以下代码添加进观察者队列
owner.getLifecycle().addObserver(this);
这行代码的内部是通过 FastSafeIterableMap 类来管理观察者的, 而这个类是非线程安全的, 如下:
我们来看看 trello/RxLifecycle,uber/AutoDispose,RxLife 这三者是如何处理这个问题的.
trello/RxLifecycle
RxLifecycle 库是 AndroidLifecycle 类感知生命周期, 简单看看源码:
- public final class AndroidLifecycle implements LifecycleProvider<Lifecycle.Event>, LifecycleObserver {
- public static LifecycleProvider<Lifecycle.Event> createLifecycleProvider(LifecycleOwner owner) {
- return new AndroidLifecycle(owner);
- }
- private final BehaviorSubject<Lifecycle.Event> lifecycleSubject = BehaviorSubject.create();
- private AndroidLifecycle(LifecycleOwner owner) {
- owner.getLifecycle().addObserver(this);
- }
- // 中间省略部分代码
- @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
- void onEvent(LifecycleOwner owner, Lifecycle.Event event) {
- lifecycleSubject.onNext(event);
- if (event == Lifecycle.Event.ON_DESTROY) {
- owner.getLifecycle().removeObserver(this);
- }
- }
- }
可以看到, RxLifecycle 是在对象创建时添加观察者, 且它没有做任何处理, 如果你在子线程使用, 就需要额外注意了, 而且它只有在页面销毁时, 才会移除观察者, 试想, 我们在首页一般都会有非常多的请求, 而这每一个请求都会有一个 AndroidLifecycle 对象, 我们想请求结束就要回收这个对象, 然而, 这个对象还是观察者队列里, 就导致了没办法回收, 如果我们不停下拉刷新, 上拉加载更多, 对内存就是一个挑战.
RxLifecycle 还有一个弊端时, 当 Activity/Fragment 销毁时, 始终会往下游发送一个 onComplete 事件, 这对于在 onComplete 事件中有业务逻辑的同学来说, 无疑是致命的打击.
uber/AutoDispose
AutoDispose 库我们看 LifecycleEventsObservable 类, 如下
- class LifecycleEventsObservable extends Observable<Event> {
- // 省略部分代码
- @Override protected void subscribeActual(Observer<? super Event> observer) {
- ArchLifecycleObserver archObserver = new ArchLifecycleObserver(lifecycle, observer, eventsObservable);
- observer.onSubscribe(archObserver);
- if (!isMainThread()) { // 非主线程, 直接抛出异常
- observer.onError(new IllegalStateException("Lifecycles can only be bound to on the main thread!"));
- return;
- }
- lifecycle.addObserver(archObserver); // 添加观察者
- if (archObserver.isDisposed()) {
- lifecycle.removeObserver(archObserver);
- }
- }
- // 省略部分代码
可以看到, AutoDispose 是在事件订阅时添加观察者, 并且当前非主线程时, 直接抛出异常, 也就说明使用 AutoDispose 不能在子线程订阅事件. 在移除观察者方面, AutoDispose 会在事件结束或者页面销毁时移除观察者, 这一点要优于 RxLifecycle.
RxLife
RxLife 库我们看 AbstractLifecycle 类, 如下:
- public abstract class AbstractLifecycle<T> extends AtomicReference<T> implements Disposable {
- // 省略部分代码
- protected final void addObserver() throws Exception {
- //Lifecycle 添加监听器需要在主线程执行
- if (isMainThread() || !(scope instanceof LifecycleScope)) {
- addObserverOnMain();
- } else {
- final Object object = mObject;
- AndroidSchedulers.mainThread().scheduleDirect(() -> {
- addObserverOnMain();
- synchronized (object) {
- isAddObserver = true;
- object.notifyAll(); // 唤醒等待的线程
- }
- });
- synchronized (object) { // 加锁等待
- while (!isAddObserver) {
- try {
- object.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- // 省略部分代码
- }
可以看到, RxLife 对子线程做了额外的操作, 在子线程通过同步锁, 添加完观察者后再往下走, 且 RxLife 同样会在事件结束或者页面销毁时移除观察者.
/ 小彩蛋 /
RxLife 类里面的 as 系列方法, 皆适用于 Observable,Flowable,ParallelFlowable,Single,Maybe,Completable 这 6 个被观察者对象, 道理都一样, 这里不在一一讲解. 另外, 在 Activity/Fragment 上, 如果你想在某个生命周期方法中断管道, 可使用 as 操作符的重载方法, 如下:
- // 在 Activity/Fragment 上
- Observable.interval(1, 1, TimeUnit.SECONDS) // 隔一秒发送一条消息
- .as(RxLife.as(this, Event.ON_STOP)) // 在 onStop 方法中断管道
- .subscribe(aLong -> {
- Log.e("LJX", "accept=" + aLong);
- });
此时如果你还想在主线程回调观察者, 使用 asOnMain 方法即可, 如下:
- // 在 Activity/Fragment 上
- Observable.interval(1, 1, TimeUnit.SECONDS) // 隔一秒发送一条消息
- .as(RxLife.asOnMain(this, Event.ON_STOP)) // 在 onStop 方法中断管道, 并在主线程回调观察者
- .subscribe(aLong -> {
- Log.e("LJX", "accept=" + aLong);
- });
- // 等同于
- Observable.interval(1, 1, TimeUnit.SECONDS) // 隔一秒发送一条消息
- .observeOn(AndroidSchedulers.mainThread())
- .as(RxLife.as(this, Event.ON_STOP)) // 在 onStop 方法中断管道, 并在主线程回调观察者
- .subscribe(aLong -> {
- Log.e("LJX", "accept=" + aLong);
- });
- / 写在最后 /
在 Activity/Fragment/View 中, 无需做任何准备工作就可以直接使用 RxLife.as(this), 然而在 ViewModel 及任意类, 需要分别继承 ScopeViewModel 及 BaseScope 类才可以使用 RxLife.as(this), 这多少都带有点侵入性, 但这也是没有办法的办法, 如果你觉得这样不能接受, RxLife 允许你自行去实现 Scope 接口.
注: 一定要使用 ViewModelProviders 获取 ViewModel 对象, 如下:
- // 在 Activity/Fragment 上
- MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)
如文章中若有疏漏之处, 请广大读者指正, RxLife 刚出来不久, 使用过程中如有遇到问题, 请在 GitHub 上留言.
GitHub 示例:
https://github.com/liujingxing/RxLife
读者福利分享
Android 开发资料 + 面试架构资料 免费分享 点击链接 即可领取
《Android 架构师必备学习资源免费领取 (架构视频 + 面试专题文档 + 学习笔记)》
来源: http://www.jianshu.com/p/1cd51fdd2835