RxJava2 實戰知識梳理(10) 屏幕旋轉致使 Activity 重建時恢復任務

1、前言

若是咱們在AndroidManifest.xml中聲明Activity時,沒有對android:configChanges進行特殊的聲明,那麼在屏幕旋轉時,會致使Activity的重建,幾個關鍵聲明週期的調用狀況以下所示: html

旋轉屏幕前的 Activity中的變量都會被銷燬,可是有時候咱們某些任務的執行不和 Activity的生命週期綁定,這時候咱們就能夠利用 Fragment提供的 setRetainInstance方法,該方法的說明以下:
setRetainInstance 方法說明
若是給 Fragment設置了該標誌位,那麼在屏幕旋轉以後,雖然它依附的 Activity被銷燬了,可是該 Fragment的實例會被保留,而且在 Activity的銷燬過程當中,只會調用該 FragmentonDetach方法,而不會調用 onDestroy方法。

而在Activity重建時,會調用該Fragment實例的onAttachonActivityCreated方法,但不會調用onCreate方法。java

根據Fragment提供的這一特性,那麼咱們就能夠將一些在屏幕旋轉過程當中,仍然須要運行的任務放在具備該屬性的Fragment中執行。在 Handling Configuration Changes with Fragments 這篇文章中,做者介紹了經過這個技巧來實現了一個不被中斷的AsyncTask,你們有須要瞭解詳細說明的能夠查看這篇文章。android

今天,咱們跟着前人腳步,用RxJava來演示在屏幕旋轉致使Activity重建時,仍然保持後臺任務繼續執行的例子。架構

2、示例

2.1 示例

首先,咱們聲明一個接口,用於FragmentActivity一個ConnectableObservable,使得Activity能夠監聽到Fragment中後臺任務的工做進度。ide

public interface IHolder {
    public void onWorkerPrepared(ConnectableObservable<Long> workerFlow);
}
複製代碼

下面,咱們來實現WorkerFragment,咱們在onCreate中建立了數據源,它每隔1s向下遊發送數據,在onResume中,經過前面定義的接口向Activity傳遞一個ConnectableObservable用於監聽。這裏最關鍵的是須要調用咱們前面說到的setRetainInstance方法,最後別忘了,在onDetach中將mHolder置爲空,不然它就會持有須要被重建的Activity示例,從而致使內存泄漏。學習

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);
                        //若是已經拋棄,那麼再也不繼續任務。
                        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,這時候就會走到WorkerFragmentonCreate方法中啓動任務,以後當WorkerFragment走到onResume方法後,就調用onWorkerPrepared,讓Activity進行訂閱,Activity就能夠收到當前任務進度的通知,來更新UI。而在任務執行完畢以後,咱們就能夠將該Fragment移除了。spa

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();
    }
}
複製代碼

咱們來演示一下屏幕旋轉時的效果,在屏幕旋轉以後,咱們仍然能夠繼續收到任務進度的更新: 3d

2.2 示例解析

下面,咱們來解釋一下示例中的幾個要點:code

2.2.1 Activity 和 Fragment 之間的數據傳遞

數據傳遞分爲兩個方向,它們各自能夠經過如下方法來實現:cdn

  • ActivityFragment傳遞數據(示例中咱們傳遞了任務的名稱) 通常用於向WorkerFragment傳遞一些任務參數,此時能夠經過FragmentsetArguments傳入相關的字段,FragmentonCreate方法中經過getArguments獲取參數。
  • FragmentActivity傳遞數據(示例中咱們傳遞了ObservableActivity訂閱以獲取進度) 可讓Activity實現一個接口,咱們在FragmentonAttach方法中獲取Activity實例轉換成對應的接口類型,以後經過它來調用Activity的方法,須要注意的是,在Fragment#onDetach時,要將該Activity的引用置空,不然會出現內存泄漏。

2.2 爲何調用 publish 方法,使用 Hot Observable 做爲 WorkerFragment 的數據源

推薦你們先看一下這篇文章 RxJava 教程第三部分:馴服數據流之 Hot & Cold Observable,這裏面對於Cold & Hot Observable進行了解釋,它們之間關鍵的區別就是:

  • 只有當訂閱者訂閱時,Cold Observale纔開始發送數據,而且每一個訂閱者都獨立執行一遍數據流代碼。
  • Hot Observable無論有沒有訂閱者,它都會發送數據流。

而在咱們的應用場景中,因爲WorkerFragment是在後臺執行任務:

  • Activity的角度來看:每次Activity重建時,在Activity中都須要用一個新的Observer實例去訂閱WorkerFragment中的數據源,所以咱們只能選擇經過Hot Observable,而不是Cold Observable來實現WorkerFragment中的數據源,不然每次都會從新執行一遍數據流的代碼,而不是繼續接收它發送的事件。
  • WorkerFragment的角度來看,它只是一個任務的執行者,無論有沒有人在監聽它的進度,它都應該執行任務。

經過Observable.create方法建立的是一個Cold Observable,該Cold Observable每隔1s發送一個事件。咱們調用publish方法來將它轉換爲Hot Observable,以後再調用該Hot Observableconnect方法讓其對源Cold Observable進行訂閱,這樣源Cold Observable就能夠開始執行任務了。而且,經過connect方法返回的Disposable對象,咱們就能夠管理轉換後的Hot Observable和源Cold Observable之間的訂閱關係。

Disposable的用途在於:在某些時候,咱們但願可以中止源Observable任務的執行,例如當該WorkerFragment真正被銷燬時,也就是執行了它的onDestroy方法,那麼咱們就能夠經過上面的Disposable取消Hot Observable對源Cold Observable的訂閱,而在Cold Observable的循環中,咱們判斷若是下游(也就是Hot Observable)取消了訂閱,那麼就再也不執行任務。

整個的架構圖以下所示:

2.3 在任務執行以後 removeFragment

在了能讓WorkerFragment能正常進行下一次任務的執行,咱們須要在發生錯誤或者任務完成的時候,經過remove的方式銷燬WorkerFragment


更多文章,歡迎訪問個人 Android 知識梳理系列:

相關文章
相關標籤/搜索