Android 應用中,後台任務(下載、播放進度)需持續更新前台 UI,且不因 Activity 重建(旋轉屏幕、配置變更)而重置或洩漏。本文展示如何結合 Fragment.setRetainInstance(true) 與 RxJava 實現狀態保持與無縫恢復。
// Activity 中獲取/創建 Retained Fragment
FragmentManager fm = getFragmentManager();
mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(RETAINED_FRAGMENT);
if (mRetainedFragment == null) {
mRetainedFragment = new RetainedFragment();
fm.beginTransaction().add(mRetainedFragment, RETAINED_FRAGMENT).commit();
}
// RetainedFragment.java
public class RetainedFragment extends Fragment {
private CustomAsyncTask mCustomAsyncTask;
private Observable<Long> mObservable;
private PublishSubject<Long> mSubject;
private int mMode;
private boolean mBusy;
// getter/setter 省略
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); // 關鍵:配置變更時不銷毀
}
}
@Override
protected void onResume() {
super.onResume();
// 從 RetainedFragment 取回狀態
mMode = mRetainedFragment.getMode();
mCustomAsyncTask = mRetainedFragment.getCustomAsyncTask();
mObservable = mRetainedFragment.getObservable();
mSubject = mRetainedFragment.getSubject();
mSubscriber = createSubscriber(); // 新建訂閱者綁定當前 Activity UI
switch (mMode) {
case ASYNC_TASK:
if (mCustomAsyncTask != null && !mCustomAsyncTask.isCompleted()) {
mCustomAsyncTask.setActivity(this); // 綁定新 Activity
}
break;
case TIME_INTERVAL:
if (mObservable != null) {
mObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.take(MAX_PROGRESS)
.map(x -> x + 1)
.subscribe(mSubscriber);
}
break;
case DELAY_EMIT:
if (mObservable != null) {
mObservable.subscribeOn(Schedulers.io())
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}
break;
case CUSTOM_ITERATOR:
if (mSubject != null) {
mSubject.subscribe(mSubscriber);
}
break;
}
setBusy(mRetainedFragment.isBusy());
}
關鍵點:在 onResume 而非 onCreate 綁定新 Activity,確保 onRestoreInstanceState 已恢復 UI 狀態(如 ProgressBar 進度)。
| 模式 | 實現 | 旋轉屏幕行為 | 適用場景 |
|---|---|---|---|
| 時間間隔 | Observable.interval(1, TimeUnit.SECONDS) | 重新發射,進度歸零重來 | 輪詢、心跳 |
| 延遲發射 | Observable.create() + delay() | 完成當前項後重新開始 | 定時單次任務 |
| 定制迭代器 | Observable.from(Iterable) + PublishSubject | 無縫銜接,進度不中斷 | 長時間下載、播放進度 |
// 1. 定義可迭代對象,封裝發射邏輯
public class CustomIterator implements Iterable<Long> {
private final List<Long> numbers = new ArrayList<>();
public CustomIterator() {
for (long i = 1; i <= MAX_PROGRESS; i++) numbers.add(i);
}
@Override
public Iterator<Long> iterator() {
return new Iterator<Long>() {
int index = 0;
@Override public boolean hasNext() { return index < numbers.size(); }
@Override public Long next() {
SystemClock.sleep(EMIT_DELAY_MS); // 模擬耗時
return numbers.get(index++);
}
@Override public void remove() { throw new UnsupportedOperationException(); }
};
}
}
// 2. Activity 啟動時
private void handleCustomIteratorClick() {
mObservable = Observable.from(new CustomIterator());
mSubject = PublishSubject.create();
// 關鍵:Observable 訂閱 Subject,Subject 再訂閱 UI Subscriber
mObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubject);
mSubject.subscribe(createSubscriber());
// 保存 Subject 與 Observable 到 RetainedFragment
mRetainedFragment.setObservable(mObservable);
mRetainedFragment.setSubject(mSubject);
mRetainedFragment.setMode(CUSTOM_ITERATOR);
}
原理:PublishSubject 既是 Observer 又是 Observable,作為熱觀察者緩衝上游發射,下游(新 Activity 的 Subscriber)訂閱時可接收後續事件,實現斷點續傳效果。
WeakReference<Activity>),擴展性差,適合簡單更新。LocalBroadcastManager 回傳進度,重量級,適合複雜業務邏輯。
