RxJava記憶體洩漏的一種解決方案
RxJava大家應該都用過或者聽過,在用RxJava的時候當被觀察者(Observable)和觀察者(Observer)產生訂閱關係後沒有及時釋放這種subscription就很容易產生記憶體洩漏,一個典型的場景就是使用RxJava發起網路請求,此時應用程式被殺掉,這種訂閱關係就沒有得到及時釋放。當然這種情況在onDestroy中手動進行判斷也行。如果是這種場景,發起的網路請求還沒成功返回,此時應用進入後臺,這時候就算請求成功返回也不應該更新UI,這種情況在使用RxJava的情況下怎麼處理?
我們遇到的情況早有大神提供瞭解決方案,就是RxLifecycle
開源庫,官方的定義:
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.
簡單來講就是可以用來處理RxJava產生的記憶體洩漏。今天我們主要看下RxLifecycle
的幾種使用方式。後面有機會再分析下原始碼。
主要有下面幾種使用方式:
1 bindToLifecycle
2 bindUntilEvent
3 LifecycleProvider
首先需要在模組的build.gradle中新增依賴:
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-navi:2.1.0'
1.bindToLifecycle
這種方式可以自動根據Activity或者Fragment的生命週期進行解綁,用起來也很方便,Avtivity需要繼承RxActivity, Fragment則需要繼承RxFragment
public class MainActivity extends BaseActivity{
@Override
protected void onStart() {
super.onStart();
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.compose(this.<Long>bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
Timber.tag(TAG).d("onStart, running num : " + num);
}
});
}
}
這裡在onStart中進行繫結,如果Activity進入onStop生命週期的時候就會停止Observable
,看一下日誌:
bindToLifecycle
就是會自動繫結生命週期,我們看下Activity
生命週期,很明顯在onCreate中bindToLifecycle
就會在onDestroy
中進行解綁,其他的一一對應就是。
/**
* Lifecycle events that can be emitted by Activities.
*/
public enum ActivityEvent {
CREATE,
START,
RESUME,
PAUSE,
STOP,
DESTROY
}
Fragment
也有對應的生命週期,也是對稱對應的。
/**
* Lifecycle events that can be emitted by Fragments.
*/
public enum FragmentEvent {
ATTACH,
CREATE,
CREATE_VIEW,
START,
RESUME,
PAUSE,
STOP,
DESTROY_VIEW,
DESTROY,
DETACH
}
2.bindUntilEvent
見名知意,就是可以和制定的生命週期進行繫結,這種方式比上面的靈活,比如可以在一個按鈕中繫結onStart
的事件,而不必要一定要解除安裝onStart
中。
我在上面的Activity中新增一個按鈕,點選事件在getData(View view)
中,看下程式碼:
public void getData(View view) {
Observable observable = Observable.interval(1, TimeUnit.SECONDS).
subscribeOn(Schedulers.io()).compose(this.bindUntilEvent(ActivityEvent.PAUSE));
observable.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
Timber.tag(TAG).d("getData, running until num : " + num);
}
});
}
按我們的預期,就是在Activity進入onPause時,Observable會停止傳送資料,看下列印的日誌是不是這樣的:
基本上面這兩種方式就夠用了,下面的方式LifecycleProvider
在MVP的模式中用處就比較大了。我們接著往下看。
3.LifecycleProvider
使用方式就是,首先繼承NaviActivity
,然後在Activity中加上這句話
LifecycleProvider<ActivityEvent> provider = NaviLifecycle.createActivityLifecycleProvider(this);
這樣就可以通過provider監聽生命週期了。我這裡在初始化presenter的時候傳遞過去
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
//初始化Presenter
presenter = new Presenter(provider);
}
在Activity中新增一個按鈕,通過延時3秒模擬網路請求,請求成功後更新UI。這裡通過 provider
傳送生命週期事件,然後在onNext
中判斷事件型別,如果已經Activity已經進入onPause onStop onDestroy
中的其中一個,就不再更新UI了,並且通過Disposable
斷開連線。
FactoryModel.getModel(Token.STRING_MODEL).params(params).execute(new CallBack<String>() {
@Override
public void onSuccess(final String data) {
provider.lifecycle().subscribe(new Observer<ActivityEvent>() {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(ActivityEvent activityEvent) {
Timber.tag("Presenter_TAG").i("received activityEvent, activityEvent = %s" , activityEvent.name());
if (null != disposable && disposable.isDisposed()){
Timber.tag("Presenter_TAG").i("disposable isDisposed");
return;
}
if (activityEvent == ActivityEvent.PAUSE || activityEvent == ActivityEvent.STOP || activityEvent == ActivityEvent.DESTROY){
Timber.tag("Presenter_TAG").e("do not refresh UI, activityEvent = %s", activityEvent.name());
onComplete();
return;
}
if (isViewAttached()) {
Timber.tag("Presenter_TAG").i("refresh UI, activityEvent = %s" , activityEvent.name());
view.showData(data);
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
if (null != disposable && !disposable.isDisposed()){
disposable.dispose();
Timber.tag("Presenter_TAG").d("LifecycleProvider disposed");
}
}
});
}
});
看一下正常請求成功的日誌,和預期一樣,正常更新UI了。
下面我們來搞一下破壞,在剛發起網路請求後,按home按鍵將APP切到後臺,這樣Activity就進入onStop
的生命週期,那麼即使網路請求成功也應該再更新UI了,看下日誌是不是這樣的:
最終UI也是沒有更新的。我們在下面的邏輯中切斷了訂閱關係,那麼下次再次點選按鈕發起網路請求是還能正常使用
provider
嗎?
if (activityEvent == ActivityEvent.PAUSE || activityEvent == ActivityEvent.STOP || activityEvent == ActivityEvent.DESTROY){
Timber.tag("Presenter_TAG").e("do not refresh UI, activityEvent = %s", activityEvent.name());
onComplete();
return;
}
其實是可以的,看一下Demo的日誌:
那麼是怎麼回事?其實就在下面這個程式碼中:
provider.lifecycle()
在獲取Observable時會清楚原先的特徵,我們看下原始碼:
private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();
@Override
@NonNull
@CheckResult
public Observable<ActivityEvent> lifecycle() {
return lifecycleSubject.hide();
}
再跟進去BehaviorSubject
,其中以後一句註釋Hides the identity of this Observable and its Disposable
,最後會返回一個新的Observable :
/**
* Hides the identity of this Observable and its Disposable.
* <p>Allows hiding extra features such as {@link io.reactivex.subjects.Subject}'s
* {@link Observer} methods or preventing certain identity-based
* optimizations (fusion).
* <dl>
* <dt><b>Scheduler:</b></dt>
* <dd>{@code hide} does not operate by default on a particular {@link Scheduler}.</dd>
* </dl>
* @return the new Observable instance
*
* @since 2.0
*/
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Observable<T> hide() {
return RxJavaPlugins.onAssembly(new ObservableHide<T>(this));
}
4.總結
RxLifecycle
的侵入性還是比較低的,基本不需要改動原來的程式碼就可以實現生命週期的監聽,也提供了防止RxJava訂閱關係記憶體洩漏的另外一種解決方案,還是很不錯的。
今天的分享就到這了,後面有機會和大家分享下RxLifecycle
的原始碼。
平時工作也比較忙,寫部落格真的是需要耐力,如果對大家有幫助歡迎關注和點贊哈。
最後感謝@右傾傾的理解和支援哈。
以上!
相關文章
- Handler記憶體洩漏原因及解決方案記憶體
- Java記憶體洩漏解決之道Java記憶體
- Handler記憶體洩漏分析及解決記憶體
- Android 輕鬆解決記憶體洩漏Android記憶體
- 解決記憶體洩漏(1)-ApacheKylin InternalThreadLocalMap洩漏問題分析記憶體Apachethread
- 萬惡的前端記憶體洩漏及萬善的解決方案前端記憶體
- Android Native 記憶體洩漏系統化解決方案Android記憶體
- 記一則伺服器記憶體洩漏解決過程伺服器記憶體
- 解決NSTimer迴圈引用導致記憶體洩漏的六種方法記憶體
- Handler的使用、記憶體洩漏和解決記憶體
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- 記憶體洩漏的原因記憶體
- Android技術分享| Android 中部分記憶體洩漏示例及解決方案Android記憶體
- 深入瞭解 JavaScript 記憶體洩漏JavaScript記憶體
- js記憶體洩漏JS記憶體
- Android記憶體洩漏Android記憶體
- Android 記憶體洩漏Android記憶體
- jvm 記憶體洩漏JVM記憶體
- Java記憶體洩漏Java記憶體
- 1.記憶體優化(一)記憶體洩漏記憶體優化
- 手把手教你解決 Flutter engine 記憶體洩漏Flutter記憶體
- WebView引起的記憶體洩漏WebView記憶體
- 記一次透過Memory Analyzer分析記憶體洩漏的解決過程記憶體
- 【譯】JavaScript的記憶體管理和 4 種處理記憶體洩漏的方法JavaScript記憶體
- 一次 Java 記憶體洩漏的排查Java記憶體
- valgrind 記憶體洩漏分析記憶體
- 解決Android記憶體洩漏;輕鬆降低100MAndroid記憶體
- 記憶體的分配與釋放,記憶體洩漏記憶體
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位
- 【譯】JavaScript的工作原理:記憶體管理和4種常見的記憶體洩漏JavaScript記憶體
- vue使用中的記憶體洩漏Vue記憶體
- Android中的記憶體洩漏模式Android記憶體模式
- [譯] Swift 中的記憶體洩漏Swift記憶體
- Android中常見的記憶體洩漏Android記憶體
- Swift的ARC和記憶體洩漏Swift記憶體
- .NET 記憶體洩漏的爭議記憶體
- ThreadLocal原理用法詳解ThreadLocal記憶體洩漏thread記憶體
- 記一次堆外記憶體洩漏分析記憶體