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

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

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

而在Activity重建時腿箩,會調(diào)用該Fragment實例的onAttachonActivityCreated方法劣摇,但不會調(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 示例

首先钮孵,我們聲明一個接口骂倘,用于FragmentActivity一個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概漱,這時候就會走到WorkerFragmentonCreate方法中啟動任務,之后當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):

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市棍鳖,隨后出現(xiàn)的幾起案子炮叶,更是在濱河造成了極大的恐慌,老刑警劉巖渡处,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜悉,死亡現(xiàn)場離奇詭異,居然都是意外死亡医瘫,警方通過查閱死者的電腦和手機侣肄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醇份,“玉大人稼锅,你說我怎么就攤上這事×欧祝” “怎么了矩距?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長怖竭。 經(jīng)常有香客問我锥债,道長,這世上最難降的妖魔是什么痊臭? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任哮肚,我火速辦了婚禮,結(jié)果婚禮上趣兄,老公的妹妹穿的比我還像新娘绽左。我一直安慰自己,他們只是感情好艇潭,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布拼窥。 她就那樣靜靜地躺著,像睡著了一般蹋凝。 火紅的嫁衣襯著肌膚如雪鲁纠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天鳍寂,我揣著相機與錄音改含,去河邊找鬼。 笑死迄汛,一個胖子當著我的面吹牛捍壤,可吹牛的內(nèi)容都是我干的骤视。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼鹃觉,長吁一口氣:“原來是場噩夢啊……” “哼专酗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盗扇,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤祷肯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疗隶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佑笋,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年斑鼻,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒋纬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡坚弱,死狀恐怖颠锉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情史汗,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布拒垃,位于F島的核電站停撞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悼瓮。R本人自食惡果不足惜戈毒,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望横堡。 院中可真熱鬧埋市,春花似錦、人聲如沸命贴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胸蛛。三九已至污茵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間葬项,已是汗流浹背泞当。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留民珍,地道東北人襟士。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓盗飒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陋桂。 傳聞我的和親對象是個殘疾皇子逆趣,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361

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