RxJava2 實(shí)戰(zhàn)知識梳理(10) - 屏幕旋轉(zhuǎn)導(dǎo)致 Activity 重建時(shí)恢復(fù)任務(wù)

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方法,該方法的說明如下:
setRetainInstance 方法說明

如果給Fragment設(shè)置了該標(biāo)志位,那么在屏幕旋轉(zhuǎn)之后,雖然它依附的Activity被銷毀了,但是該Fragment的實(shí)例會被保留,并且在Activity的銷毀過程中,只會調(diào)用該FragmentonDetach方法,而不會調(diào)用onDestroy方法。

而在Activity重建時(shí),會調(diào)用該Fragment實(shí)例的onAttachonActivityCreated方法,但不會調(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è)接口,用于FragmentActivity一個(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í)候就會走到WorkerFragmentonCreate方法中啟動任務(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):

  • ActivityFragment傳遞數(shù)據(jù)(示例中我們傳遞了任務(wù)的名稱)
    一般用于向WorkerFragment傳遞一些任務(wù)參數(shù),此時(shí)可以通過FragmentsetArguments傳入相關(guān)的字段,FragmentonCreate方法中通過getArguments獲取參數(shù)。
  • FragmentActivity傳遞數(shù)據(jù)(示例中我們傳遞了ObservableActivity訂閱以獲取進(jìn)度)
    可以讓Activity實(shí)現(xiàn)一個(gè)接口,我們在FragmentonAttach方法中獲取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 Observableconnect方法讓其對源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 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容