1.爲什麼會發生內存泄漏
使用RxJava發佈一個訂閱後,當頁面被finish,此時訂閱邏輯還未完成,如果沒有及時取消訂閱,就會導致Activity/Fragment無法被回收,從而引發內存泄漏。
寫段代碼測試一下,定義一個Activity,佈局中顯示一張圖片,這樣可以直觀的看到此Activity的內存佔用情況,然後在Activity中發佈一個訂閱後,關閉Activity,訂閱邏輯如下:
// 每隔1s執行一次事件
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Long aLong) {
Log.i("接收數據", String.valueOf(aLong));
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
看下打開Activity之前的內存佔用情況:
打開Activity之後的內存佔用情況:
-
關閉Activity,手動執行GC(點擊小車圖標),發現內存佔用並沒有減少:
導出hprof文件進行分析(點擊小車圖標右邊的圖標),發現已經發生了內存泄漏:
那麼除了在onDestory方法中手動取消訂閱之外,還有什麼方法可以避免上述的泄漏問題呢,這時RxLifecycle就派上用場了。
2.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.
大概意思就是:可以通過綁定生命週期的方式,來解決內存泄漏的問題。
3.實踐
看下使用RxLifecycle需要依賴的庫:
// RxLifecycle基礎庫
compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
// Android使用的庫,裏面使用了Android的生命週期方法
// 內部引用了基礎庫,如果使用此庫則無需再引用基礎庫
compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.1.0'
// Android組件庫,裏面定義了例如RxAppCompatActivity、RxFragment之類的Android組件
// 內部引用了基礎庫和Android庫,如果使用此庫則無需再重複引用
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
// Android使用的庫,繼承NaviActivity使用
compile 'com.trello.rxlifecycle2:rxlifecycle-navi:2.1.0'
// Android使用的庫,繼承LifecycleActivity使用
// 需要引入Google的倉庫支持,用法和rxlifecycle-navi類似
compile 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle:2.1.0'
// Google的倉庫支持
allprojects {
repositories {
jcenter()
maven { url 'https://dl.google.com/dl/android/maven2/' }
}
}
// 支持Kotlin語法的RxLifecycle基礎庫
compile 'com.trello.rxlifecycle2:rxlifecycle-kotlin:2.1.0'
// 支持Kotlin語法的Android庫
compile 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle-kotlin:2.1.0'
本文需要依賴其中兩個庫:
// 依賴以下兩個庫,會自動引用基礎庫與Android庫
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-navi:2.1.0'
rxlifecycle-components
public class RxLifecycleComponentsActivity extends RxAppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rxlifecycle_components);
}
@Override
protected void onStart() {
super.onStart();
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Long>bindToLifecycle())
.subscribe();
}
}
使用compose(this.bindToLifecycle())方法綁定Activity的生命週期,在onStart方法中綁定,在onStop方法被調用後就會解除綁定,以此類推。
有一種特殊情況,如果在onPause/onStop方法中綁定,那麼就會在它的下一個生命週期方法(onStop/onDestory)被調用後解除綁定。
再次運行程序,打開關閉Acitivity,手動進行GC,發現佔用內存減少了,導出hprof文件進行分析,沒有發生內存泄漏。
除了使用bindToLifecycle的方式之外,還可以指定取消訂閱的時機:
public class RxLifecycleComponentsActivity extends RxAppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rxlifecycle_components);
ButterKnife.bind(this);
initData();
}
private void initData() {
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe();
}
}
使用compose(this.bindUntilEvent(ActivityEvent.DESTROY))方法指定在onDestroy方法被調用時取消訂閱。
注意compose方法需要在subscribeOn方法之後使用,因爲在測試的過程中發現,將compose方法放在subscribeOn方法之前,如果在被觀察者中執行了阻塞方法,比如Thread.sleep(),取消訂閱後該阻塞方法不會被中斷。
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<String>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe();
rxlifecycle-navi
public class RxLifecycleNaviActivity extends NaviActivity {
private final LifecycleProvider<ActivityEvent> provider
= NaviLifecycle.createActivityLifecycleProvider(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rxlifecycle_navi);
ButterKnife.bind(this);
initData();
}
private void initData() {
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(provider.<Long>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Long aLong) {
Log.i("接收數據", String.valueOf(aLong));
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
@Override
protected void onStart() {
super.onStart();
Observable.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(provider.<Long>bindToLifecycle())
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Long aLong) {
Log.i("接收數據", String.valueOf(aLong));
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
繼承了NaviActivity,通過NaviLifecycle.createActivityLifecycleProvider(this)方法獲取LifecycleProvider對象,有了LifecycleProvider對象之後就可以調用bindToLifecycle或者bindUntilEvent方法了。
如果你使用的是MVP結構,這個LifecycleProvider對象可以直接傳給Presenter層使用。
繼承RxAppCompatActivity爲什麼就能直接用this的方式調用bindToLifecycle或bindUntilEvent方法呢?看下源碼,原來RxAppCompatActivity實現了LifecycleProvider接口。
public abstract class RxAppCompatActivity extends AppCompatActivity implements LifecycleProvider<ActivityEvent> {
}
4.寫在最後
源碼已託管到GitHub上,歡迎Fork,覺得還不錯就Start一下吧!
歡迎同學們吐槽評論,如果你覺得本篇博客對你有用,那麼就留個言或者頂一下吧(^-^)