一、前言
如果我們在AndroidManifest.xml
中宣告Activity
時,沒有對android:configChanges
進行特殊的宣告,那麼在螢幕旋轉時,會導致Activity
的重建,幾個關鍵宣告週期的呼叫情況如下所示:
Activity
中的變數都會被銷燬,但是有時候我們某些任務的執行不和Activity
的生命週期繫結,這時候我們就可以利用Fragment
提供的setRetainInstance
方法,該方法的說明如下:
如果給Fragment
設定了該標誌位,那麼在螢幕旋轉之後,雖然它依附的Activity
被銷燬了,但是該Fragment
的例項會被保留,並且在Activity
的銷燬過程中,只會呼叫該Fragment
的onDetach
方法,而不會呼叫onDestroy
方法。
而在Activity
重建時,會呼叫該Fragment
例項的onAttach
、onActivityCreated
方法,但不會呼叫onCreate
方法。
根據Fragment
提供的這一特性,那麼我們就可以將一些在螢幕旋轉過程中,仍然需要執行的任務放在具有該屬性的Fragment
中執行。在 Handling Configuration Changes with Fragments 這篇文章中,作者介紹了通過這個技巧來實現了一個不被中斷的AsyncTask
,大家有需要了解詳細說明的可以檢視這篇文章。
今天,我們跟著前人腳步,用RxJava
來演示在螢幕旋轉導致Activity
重建時,仍然保持後臺任務繼續執行的例子。
二、示例
2.1 示例
首先,我們宣告一個介面,用於Fragment
向Activity
一個ConnectableObservable
,使得Activity
可以監聽到Fragment
中後臺任務的工作進度。
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
,這時候就會走到WorkerFragment
的onCreate
方法中啟動任務,之後當WorkerFragment
走到onResume
方法後,就呼叫onWorkerPrepared
,讓Activity
進行訂閱,Activity
就可以收到當前任務進度的通知,來更新UI
。而在任務執行完畢之後,我們就可以將該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();
}
}
複製程式碼
我們來演示一下螢幕旋轉時的效果,在螢幕旋轉之後,我們仍然可以繼續收到任務進度的更新:
2.2 示例解析
下面,我們來解釋一下示例中的幾個要點:
2.2.1 Activity 和 Fragment 之間的資料傳遞
資料傳遞分為兩個方向,它們各自可以通過以下方法來實現:
Activity
向Fragment
傳遞資料(示例中我們傳遞了任務的名稱) 一般用於向WorkerFragment
傳遞一些任務引數,此時可以通過Fragment
的setArguments
傳入相關的欄位,Fragment
在onCreate
方法中通過getArguments
獲取引數。Fragment
向Activity
傳遞資料(示例中我們傳遞了Observable
供Activity
訂閱以獲取進度) 可以讓Activity
實現一個介面,我們在Fragment
的onAttach
方法中獲取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 Observable
的connect
方法讓其對源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 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/