RxJava2 實(shí)戰(zhàn)系列文章
RxJava2 實(shí)戰(zhàn)知識梳理(1) - 后臺執(zhí)行耗時(shí)操作,實(shí)時(shí)通知 UI 更新
RxJava2 實(shí)戰(zhàn)知識梳理(2) - 計(jì)算一段時(shí)間內(nèi)數(shù)據(jù)的平均值
RxJava2 實(shí)戰(zhàn)知識梳理(3) - 優(yōu)化搜索聯(lián)想功能
RxJava2 實(shí)戰(zhàn)知識梳理(4) - 結(jié)合 Retrofit 請求新聞資訊
RxJava2 實(shí)戰(zhàn)知識梳理(5) - 簡單及進(jìn)階的輪詢操作
RxJava2 實(shí)戰(zhàn)知識梳理(6) - 基于錯(cuò)誤類型的重試請求
RxJava2 實(shí)戰(zhàn)知識梳理(7) - 基于 combineLatest 實(shí)現(xiàn)的輸入表單驗(yàn)證
RxJava2 實(shí)戰(zhàn)知識梳理(8) - 使用 publish + merge 優(yōu)化先加載緩存,再讀取網(wǎng)絡(luò)數(shù)據(jù)的請求過程
RxJava2 實(shí)戰(zhàn)知識梳理(9) - 使用 timer/interval/delay 實(shí)現(xiàn)任務(wù)調(diào)度
RxJava2 實(shí)戰(zhàn)知識梳理(10) - 屏幕旋轉(zhuǎn)導(dǎo)致 Activity 重建時(shí)恢復(fù)任務(wù)
RxJava2 實(shí)戰(zhàn)知識梳理(11) - 檢測網(wǎng)絡(luò)狀態(tài)并自動重試請求
RxJava2 實(shí)戰(zhàn)知識梳理(12) - 實(shí)戰(zhàn)講解 publish & replay & share & refCount & autoConnect
RxJava2 實(shí)戰(zhàn)知識梳理(13) - 如何使得錯(cuò)誤發(fā)生時(shí)不自動停止訂閱關(guān)系
RxJava2 實(shí)戰(zhàn)知識梳理(14) - 在 token 過期時(shí),刷新過期 token 并重新發(fā)起請求
RxJava2 實(shí)戰(zhàn)知識梳理(15) - 實(shí)現(xiàn)一個(gè)簡單的 MVP + RxJava + Retrofit 應(yīng)用
一、前言
如果我們在AndroidManifest.xml
中聲明Activity
時(shí),沒有對android:configChanges
進(jìn)行特殊的聲明,那么在屏幕旋轉(zhuǎn)時(shí),會導(dǎo)致Activity
的重建,幾個(gè)關(guān)鍵聲明周期的調(diào)用情況如下所示:
旋轉(zhuǎn)屏幕前的
Activity
中的變量都會被銷毀,但是有時(shí)候我們某些任務(wù)的執(zhí)行不和Activity
的生命周期綁定,這時(shí)候我們就可以利用Fragment
提供的setRetainInstance
方法,該方法的說明如下:如果給
Fragment
設(shè)置了該標(biāo)志位,那么在屏幕旋轉(zhuǎn)之后,雖然它依附的Activity
被銷毀了,但是該Fragment
的實(shí)例會被保留,并且在Activity
的銷毀過程中,只會調(diào)用該Fragment
的onDetach
方法,而不會調(diào)用onDestroy
方法。
而在Activity
重建時(shí),會調(diào)用該Fragment
實(shí)例的onAttach
、onActivityCreated
方法,但不會調(diào)用onCreate
方法。
根據(jù)Fragment
提供的這一特性,那么我們就可以將一些在屏幕旋轉(zhuǎn)過程中,仍然需要運(yùn)行的任務(wù)放在具有該屬性的Fragment
中執(zhí)行。在 Handling Configuration Changes with Fragments 這篇文章中,作者介紹了通過這個(gè)技巧來實(shí)現(xiàn)了一個(gè)不被中斷的AsyncTask
,大家有需要了解詳細(xì)說明的可以查看這篇文章。
今天,我們跟著前人腳步,用RxJava
來演示在屏幕旋轉(zhuǎn)導(dǎo)致Activity
重建時(shí),仍然保持后臺任務(wù)繼續(xù)執(zhí)行的例子。
二、示例
2.1 示例
首先,我們聲明一個(gè)接口,用于Fragment
向Activity
一個(gè)ConnectableObservable
,使得Activity
可以監(jiān)聽到Fragment
中后臺任務(wù)的工作進(jìn)度。
public interface IHolder {
public void onWorkerPrepared(ConnectableObservable<Long> workerFlow);
}
下面,我們來實(shí)現(xiàn)WorkerFragment
,我們在onCreate
中創(chuàng)建了數(shù)據(jù)源,它每隔1s
向下游發(fā)送數(shù)據(jù),在onResume
中,通過前面定義的接口向Activity
傳遞一個(gè)ConnectableObservable
用于監(jiān)聽。這里最關(guān)鍵的是需要調(diào)用我們前面說到的setRetainInstance
方法,最后別忘了,在onDetach
中將mHolder
置為空,否則它就會持有需要被重建的Activity
示例,從而導(dǎo)致內(nèi)存泄漏。
public class WorkerFragment extends Fragment {
public static final String TAG = WorkerFragment.class.getName();
private ConnectableObservable<String> mWorker;
private Disposable mDisposable;
private IHolder mHolder;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof IHolder) {
mHolder = (IHolder) context;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (mWorker != null) {
return;
}
Bundle bundle = getArguments();
final String taskName = (bundle != null ? bundle.getString("task_name") : null);
mWorker = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
for (int i = 0; i < 10; i++) {
String message = "任務(wù)名稱=" + taskName + ", 任務(wù)進(jìn)度=" + i * 10 + "%";
try {
Log.d(TAG, message);
Thread.sleep(1000);
//如果已經(jīng)拋棄,那么不再繼續(xù)任務(wù)。
if (observableEmitter.isDisposed()) {
break;
}
} catch (InterruptedException error) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(error);
}
}
observableEmitter.onNext(message);
}
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io()).publish();
mDisposable = mWorker.connect();
}
@Override
public void onResume() {
super.onResume();
if (mHolder != null) {
mHolder.onWorkerPrepared(mWorker);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mDisposable.dispose();
Log.d(TAG, "onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
mHolder = null;
}
}
最后來看Activity
,當(dāng)點(diǎn)擊“開始工作任務(wù)”后,我們嘗試添加WorkerFragment
,這時(shí)候就會走到WorkerFragment
的onCreate
方法中啟動任務(wù),之后當(dāng)WorkerFragment
走到onResume
方法后,就調(diào)用onWorkerPrepared
,讓Activity
進(jìn)行訂閱,Activity
就可以收到當(dāng)前任務(wù)進(jìn)度的通知,來更新UI
。而在任務(wù)執(zhí)行完畢之后,我們就可以將該Fragment
移除了。
public class RotationPersistActivity extends AppCompatActivity implements IHolder {
private static final String TAG = RotationPersistActivity.class.getName();
private Button mBtWorker;
private TextView mTvResult;
private CompositeDisposable mCompositeDisposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_rotation_persist);
mBtWorker = (Button) findViewById(R.id.bt_start_worker);
mBtWorker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startWorker();
}
});
mTvResult = (TextView) findViewById(R.id.tv_worker_result);
mCompositeDisposable = new CompositeDisposable();
}
@Override
public void onWorkerPrepared(Observable<String> worker) {
DisposableObserver<String> disposableObserver = new DisposableObserver<String>() {
@Override
public void onNext(String message) {
mTvResult.setText(message);
}
@Override
public void onError(Throwable throwable) {
onWorkerFinished();
mTvResult.setText("任務(wù)錯(cuò)誤");
}
@Override
public void onComplete() {
onWorkerFinished();
mTvResult.setText("任務(wù)完成");
}
};
worker.observeOn(AndroidSchedulers.mainThread()).subscribe(disposableObserver);
mCompositeDisposable.add(disposableObserver);
}
private void startWorker() {
WorkerFragment worker = getWorkerFragment();
if (worker == null) {
addWorkerFragment();
} else {
Log.d(TAG, "WorkerFragment has attach");
}
}
private void onWorkerFinished() {
Log.d(TAG, "onWorkerFinished");
removeWorkerFragment();
}
private void addWorkerFragment() {
WorkerFragment workerFragment = new WorkerFragment();
Bundle bundle = new Bundle();
bundle.putString("task_name", "學(xué)習(xí)RxJava2");
workerFragment.setArguments(bundle);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(workerFragment, WorkerFragment.TAG);
transaction.commit();
}
private void removeWorkerFragment() {
WorkerFragment workerFragment = getWorkerFragment();
if (workerFragment != null) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.remove(workerFragment);
transaction.commit();
}
}
private WorkerFragment getWorkerFragment() {
FragmentManager manager = getSupportFragmentManager();
return (WorkerFragment) manager.findFragmentByTag(WorkerFragment.TAG);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
mCompositeDisposable.clear();
}
}
我們來演示一下屏幕旋轉(zhuǎn)時(shí)的效果,在屏幕旋轉(zhuǎn)之后,我們?nèi)匀豢梢岳^續(xù)收到任務(wù)進(jìn)度的更新:
2.2 示例解析
下面,我們來解釋一下示例中的幾個(gè)要點(diǎn):
2.2.1 Activity 和 Fragment 之間的數(shù)據(jù)傳遞
數(shù)據(jù)傳遞分為兩個(gè)方向,它們各自可以通過以下方法來實(shí)現(xiàn):
-
Activity
向Fragment
傳遞數(shù)據(jù)(示例中我們傳遞了任務(wù)的名稱)
一般用于向WorkerFragment
傳遞一些任務(wù)參數(shù),此時(shí)可以通過Fragment
的setArguments
傳入相關(guān)的字段,Fragment
在onCreate
方法中通過getArguments
獲取參數(shù)。 -
Fragment
向Activity
傳遞數(shù)據(jù)(示例中我們傳遞了Observable
供Activity
訂閱以獲取進(jìn)度)
可以讓Activity
實(shí)現(xiàn)一個(gè)接口,我們在Fragment
的onAttach
方法中獲取Activity
實(shí)例轉(zhuǎn)換成對應(yīng)的接口類型,之后通過它來調(diào)用Activity
的方法,需要注意的是,在Fragment#onDetach
時(shí),要將該Activity
的引用置空,否則會出現(xiàn)內(nèi)存泄漏。
2.2 為什么調(diào)用 publish 方法,使用 Hot Observable 作為 WorkerFragment 的數(shù)據(jù)源
推薦大家先看一下這篇文章 RxJava 教程第三部分:馴服數(shù)據(jù)流之 Hot & Cold Observable,這里面對于Cold & Hot Observable
進(jìn)行了解釋,它們之間關(guān)鍵的區(qū)別就是:
- 只有當(dāng)訂閱者訂閱時(shí),
Cold Observale
才開始發(fā)送數(shù)據(jù),并且每個(gè)訂閱者都獨(dú)立執(zhí)行一遍數(shù)據(jù)流代碼。 - 而
Hot Observable
不管有沒有訂閱者,它都會發(fā)送數(shù)據(jù)流。
而在我們的應(yīng)用場景中,由于WorkerFragment
是在后臺執(zhí)行任務(wù):
- 從
Activity
的角度來看:每次Activity
重建時(shí),在Activity
中都需要用一個(gè)新的Observer
實(shí)例去訂閱WorkerFragment
中的數(shù)據(jù)源,因此我們只能選擇通過Hot Observable
,而不是Cold Observable
來實(shí)現(xiàn)WorkerFragment
中的數(shù)據(jù)源,否則每次都會重新執(zhí)行一遍數(shù)據(jù)流的代碼,而不是繼續(xù)接收它發(fā)送的事件。 - 從
WorkerFragment
的角度來看,它只是一個(gè)任務(wù)的執(zhí)行者,不管有沒有人在監(jiān)聽它的進(jìn)度,它都應(yīng)該執(zhí)行任務(wù)。
通過Observable.create
方法創(chuàng)建的是一個(gè)Cold Observable
,該Cold Observable
每隔1s
發(fā)送一個(gè)事件。我們調(diào)用publish
方法來將它轉(zhuǎn)換為Hot Observable
,之后再調(diào)用該Hot Observable
的connect
方法讓其對源Cold Observable
進(jìn)行訂閱,這樣源Cold Observable
就可以開始執(zhí)行任務(wù)了。并且,通過connect
方法返回的Disposable
對象,我們就可以管理轉(zhuǎn)換后的Hot Observable
和源Cold Observable
之間的訂閱關(guān)系。
Disposable
的用途在于:在某些時(shí)候,我們希望能夠停止源Observable
任務(wù)的執(zhí)行,例如當(dāng)該WorkerFragment
真正被銷毀時(shí),也就是執(zhí)行了它的onDestroy
方法,那么我們就可以通過上面的Disposable
取消Hot Observable
對源Cold Observable
的訂閱,而在Cold Observable
的循環(huán)中,我們判斷如果下游(也就是Hot Observable
)取消了訂閱,那么就不再執(zhí)行任務(wù)。
整個(gè)的架構(gòu)圖如下所示:
2.3 在任務(wù)執(zhí)行之后 removeFragment
在了能讓WorkerFragment
能正常進(jìn)行下一次任務(wù)的執(zhí)行,我們需要在發(fā)生錯(cuò)誤或者任務(wù)完成的時(shí)候,通過remove
的方式銷毀WorkerFragment
。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- 個(gè)人主頁:http://lizejun.cn
- 個(gè)人知識總結(jié)目錄:http://lizejun.cn/categories/