RxJava2 實戰(zhàn)系列文章
RxJava2 實戰(zhàn)知識梳理(1) - 后臺執(zhí)行耗時操作考廉,實時通知 UI 更新
RxJava2 實戰(zhàn)知識梳理(2) - 計算一段時間內(nèi)數(shù)據(jù)的平均值
RxJava2 實戰(zhàn)知識梳理(3) - 優(yōu)化搜索聯(lián)想功能
RxJava2 實戰(zhàn)知識梳理(4) - 結(jié)合 Retrofit 請求新聞資訊
RxJava2 實戰(zhàn)知識梳理(5) - 簡單及進階的輪詢操作
RxJava2 實戰(zhàn)知識梳理(6) - 基于錯誤類型的重試請求
RxJava2 實戰(zhàn)知識梳理(7) - 基于 combineLatest 實現(xiàn)的輸入表單驗證
RxJava2 實戰(zhàn)知識梳理(8) - 使用 publish + merge 優(yōu)化先加載緩存贾陷,再讀取網(wǎng)絡(luò)數(shù)據(jù)的請求過程
RxJava2 實戰(zhàn)知識梳理(9) - 使用 timer/interval/delay 實現(xiàn)任務調(diào)度
RxJava2 實戰(zhàn)知識梳理(10) - 屏幕旋轉(zhuǎn)導致 Activity 重建時恢復任務
RxJava2 實戰(zhàn)知識梳理(11) - 檢測網(wǎng)絡(luò)狀態(tài)并自動重試請求
RxJava2 實戰(zhàn)知識梳理(12) - 實戰(zhàn)講解 publish & replay & share & refCount & autoConnect
RxJava2 實戰(zhàn)知識梳理(13) - 如何使得錯誤發(fā)生時不自動停止訂閱關(guān)系
RxJava2 實戰(zhàn)知識梳理(14) - 在 token 過期時世曾,刷新過期 token 并重新發(fā)起請求
RxJava2 實戰(zhàn)知識梳理(15) - 實現(xiàn)一個簡單的 MVP + RxJava + Retrofit 應用
一轮蜕、前言
如果我們在AndroidManifest.xml
中聲明Activity
時,沒有對android:configChanges
進行特殊的聲明榨崩,那么在屏幕旋轉(zhuǎn)時七问,會導致Activity
的重建,幾個關(guān)鍵聲明周期的調(diào)用情況如下所示:
旋轉(zhuǎn)屏幕前的
Activity
中的變量都會被銷毀蜡镶,但是有時候我們某些任務的執(zhí)行不和Activity
的生命周期綁定雾袱,這時候我們就可以利用Fragment
提供的setRetainInstance
方法,該方法的說明如下:如果給
Fragment
設(shè)置了該標志位官还,那么在屏幕旋轉(zhuǎn)之后芹橡,雖然它依附的Activity
被銷毀了,但是該Fragment
的實例會被保留望伦,并且在Activity
的銷毀過程中林说,只會調(diào)用該Fragment
的onDetach
方法,而不會調(diào)用onDestroy
方法屯伞。
而在Activity
重建時腿箩,會調(diào)用該Fragment
實例的onAttach
、onActivityCreated
方法劣摇,但不會調(diào)用onCreate
方法珠移。
根據(jù)Fragment
提供的這一特性,那么我們就可以將一些在屏幕旋轉(zhuǎn)過程中饵撑,仍然需要運行的任務放在具有該屬性的Fragment
中執(zhí)行剑梳。在 Handling Configuration Changes with Fragments 這篇文章中,作者介紹了通過這個技巧來實現(xiàn)了一個不被中斷的AsyncTask
滑潘,大家有需要了解詳細說明的可以查看這篇文章垢乙。
今天,我們跟著前人腳步语卤,用RxJava
來演示在屏幕旋轉(zhuǎn)導致Activity
重建時追逮,仍然保持后臺任務繼續(xù)執(zhí)行的例子酪刀。
二、示例
2.1 示例
首先钮孵,我們聲明一個接口骂倘,用于Fragment
向Activity
一個ConnectableObservable
,使得Activity
可以監(jiān)聽到Fragment
中后臺任務的工作進度巴席。
public interface IHolder {
public void onWorkerPrepared(ConnectableObservable<Long> workerFlow);
}
下面历涝,我們來實現(xiàn)WorkerFragment
,我們在onCreate
中創(chuàng)建了數(shù)據(jù)源漾唉,它每隔1s
向下游發(fā)送數(shù)據(jù)荧库,在onResume
中,通過前面定義的接口向Activity
傳遞一個ConnectableObservable
用于監(jiān)聽赵刑。這里最關(guān)鍵的是需要調(diào)用我們前面說到的setRetainInstance
方法分衫,最后別忘了,在onDetach
中將mHolder
置為空般此,否則它就會持有需要被重建的Activity
示例蚪战,從而導致內(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 = "任務名稱=" + taskName + ", 任務進度=" + i * 10 + "%";
try {
Log.d(TAG, message);
Thread.sleep(1000);
//如果已經(jīng)拋棄铐懊,那么不再繼續(xù)任務邀桑。
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
,當點擊“開始工作任務”后居扒,我們嘗試添加WorkerFragment
概漱,這時候就會走到WorkerFragment
的onCreate
方法中啟動任務,之后當WorkerFragment
走到onResume
方法后喜喂,就調(diào)用onWorkerPrepared
瓤摧,讓Activity
進行訂閱,Activity
就可以收到當前任務進度的通知玉吁,來更新UI
照弥。而在任務執(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("任務錯誤");
}
@Override
public void onComplete() {
onWorkerFinished();
mTvResult.setText("任務完成");
}
};
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", "學習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)時的效果这揣,在屏幕旋轉(zhuǎn)之后,我們?nèi)匀豢梢岳^續(xù)收到任務進度的更新:
2.2 示例解析
下面影斑,我們來解釋一下示例中的幾個要點:
2.2.1 Activity 和 Fragment 之間的數(shù)據(jù)傳遞
數(shù)據(jù)傳遞分為兩個方向给赞,它們各自可以通過以下方法來實現(xiàn):
-
Activity
向Fragment
傳遞數(shù)據(jù)(示例中我們傳遞了任務的名稱)
一般用于向WorkerFragment
傳遞一些任務參數(shù),此時可以通過Fragment
的setArguments
傳入相關(guān)的字段矫户,Fragment
在onCreate
方法中通過getArguments
獲取參數(shù)片迅。 -
Fragment
向Activity
傳遞數(shù)據(jù)(示例中我們傳遞了Observable
供Activity
訂閱以獲取進度)
可以讓Activity
實現(xiàn)一個接口,我們在Fragment
的onAttach
方法中獲取Activity
實例轉(zhuǎn)換成對應的接口類型皆辽,之后通過它來調(diào)用Activity
的方法柑蛇,需要注意的是芥挣,在Fragment#onDetach
時,要將該Activity
的引用置空耻台,否則會出現(xiàn)內(nèi)存泄漏空免。
2.2 為什么調(diào)用 publish 方法,使用 Hot Observable 作為 WorkerFragment 的數(shù)據(jù)源
推薦大家先看一下這篇文章 RxJava 教程第三部分:馴服數(shù)據(jù)流之 Hot & Cold Observable盆耽,這里面對于Cold & Hot Observable
進行了解釋蹋砚,它們之間關(guān)鍵的區(qū)別就是:
- 只有當訂閱者訂閱時,
Cold Observale
才開始發(fā)送數(shù)據(jù)摄杂,并且每個訂閱者都獨立執(zhí)行一遍數(shù)據(jù)流代碼都弹。 - 而
Hot Observable
不管有沒有訂閱者,它都會發(fā)送數(shù)據(jù)流匙姜。
而在我們的應用場景中,由于WorkerFragment
是在后臺執(zhí)行任務:
- 從
Activity
的角度來看:每次Activity
重建時冯痢,在Activity
中都需要用一個新的Observer
實例去訂閱WorkerFragment
中的數(shù)據(jù)源氮昧,因此我們只能選擇通過Hot Observable
,而不是Cold Observable
來實現(xiàn)WorkerFragment
中的數(shù)據(jù)源浦楣,否則每次都會重新執(zhí)行一遍數(shù)據(jù)流的代碼袖肥,而不是繼續(xù)接收它發(fā)送的事件。 - 從
WorkerFragment
的角度來看振劳,它只是一個任務的執(zhí)行者椎组,不管有沒有人在監(jiān)聽它的進度,它都應該執(zhí)行任務历恐。
通過Observable.create
方法創(chuàng)建的是一個Cold Observable
寸癌,該Cold Observable
每隔1s
發(fā)送一個事件。我們調(diào)用publish
方法來將它轉(zhuǎn)換為Hot Observable
弱贼,之后再調(diào)用該Hot Observable
的connect
方法讓其對源Cold Observable
進行訂閱蒸苇,這樣源Cold Observable
就可以開始執(zhí)行任務了。并且吮旅,通過connect
方法返回的Disposable
對象溪烤,我們就可以管理轉(zhuǎn)換后的Hot Observable
和源Cold Observable
之間的訂閱關(guān)系。
Disposable
的用途在于:在某些時候庇勃,我們希望能夠停止源Observable
任務的執(zhí)行檬嘀,例如當該WorkerFragment
真正被銷毀時,也就是執(zhí)行了它的onDestroy
方法责嚷,那么我們就可以通過上面的Disposable
取消Hot Observable
對源Cold Observable
的訂閱鸳兽,而在Cold Observable
的循環(huán)中,我們判斷如果下游(也就是Hot Observable
)取消了訂閱再层,那么就不再執(zhí)行任務贸铜。
整個的架構(gòu)圖如下所示:
2.3 在任務執(zhí)行之后 removeFragment
在了能讓WorkerFragment
能正常進行下一次任務的執(zhí)行堡纬,我們需要在發(fā)生錯誤或者任務完成的時候,通過remove
的方式銷毀WorkerFragment
蒿秦。
更多文章烤镐,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/