在StackOverflow上這類問(wèn)題很常見(jiàn)
What is the best way to retain active objects—such as runningThreads栏尚、Sockets鳄厌、 andAsyncTasks—across device configuration changes?
回答問(wèn)題之前硕糊,我們先討論開(kāi)發(fā)者通常在處理與Activity生命周期相關(guān)的耗時(shí)任務(wù)會(huì)遇到的困難,接著,我們會(huì)討論兩種常見(jiàn)解決方法的缺陷仲吏,最后占调,我們會(huì)使用持久化Framgnet作為實(shí)例代碼,給出值得推薦解決方案法梯。
屏幕旋轉(zhuǎn) & 后臺(tái)任務(wù)
屏幕旋轉(zhuǎn)時(shí)苔货,Activity必須經(jīng)歷生命周期的重構(gòu),而事件的發(fā)生卻是不可預(yù)測(cè)的立哑。后臺(tái)并發(fā)任務(wù)的處理無(wú)異加劇了這個(gè)難題夜惭。
比如,Activity啟動(dòng)了AsyncTask之后铛绰,用戶旋轉(zhuǎn)手機(jī)屏幕诈茧,導(dǎo)致Activity被銷毀和重構(gòu)。AsyncTask完成任務(wù)后捂掰,并不知道存在新Activity敢会,錯(cuò)誤地把結(jié)果轉(zhuǎn)交給舊Activity。另一方面这嚣,新Activity并不知道AsyncTask的存在和處理結(jié)果鸥昏,會(huì)重新啟動(dòng)AsyncTask,導(dǎo)致資源浪費(fèi)姐帚。因此吏垮,在屏幕旋轉(zhuǎn)的過(guò)程中,正確有效地保存Activity信息就顯得尤為重要罐旗。
壞方法:固定Activity的方向
世界上最取巧膳汪,最被濫用的方法就是通過(guò)固定Activity方向,阻止Activity的重構(gòu)九秀。
在AndroidManifest.xml文件中設(shè)置android:configChanges
這個(gè)簡(jiǎn)單的方法非常吸引開(kāi)發(fā)者遗嗽。谷歌工程師并不推薦這種做法。
首當(dāng)其沖需要使用代碼處理屏幕旋轉(zhuǎn)鼓蜒,意味著花更多的精力確保每個(gè)字符串(string),布局(layout),尺寸(dimen)等與當(dāng)前屏幕方向保持同步痹换,處理不當(dāng)很容易會(huì)造成一系列的資源特定bug征字。
谷歌另一個(gè)不鼓勵(lì)使用該方法的原因,很多開(kāi)發(fā)者錯(cuò)誤地設(shè)置android:configChanges="orientation"
(舉例)會(huì)意外地阻止底層Activity摧毀和重構(gòu)晴音。不單止屏幕旋轉(zhuǎn)柔纵,還有各種各樣的原因會(huì)導(dǎo)致配置改變,把設(shè)備接到顯示器上锤躁、改變默認(rèn)語(yǔ)言搁料、改變默認(rèn)字體大小只是三個(gè)會(huì)改變配置的觸發(fā)事件。所以系羞,設(shè)置android:configChanges
并不是一個(gè)好方法郭计。
過(guò)時(shí),重寫onRetainNonConfigurationInstance()
在Android Honeycomb(Android 3.1系統(tǒng)椒振,譯者注)版本之前昭伸,推薦重寫onRetainNonConfigurationInstance()
和getLastNonConfigurationInstance()
在多個(gè)Activity實(shí)例間轉(zhuǎn)移對(duì)象。onRetainNonConfigurationInstance()
用于傳遞對(duì)象而getLastNonConfigurationInstance()
用于獲取對(duì)象澎迎。在API 13(Android 3.2系統(tǒng)庐杨,譯者注)這些方法過(guò)時(shí),支持使用更方便的模塊化方法Fragment中setRetainInstance(boolean)
來(lái)保存對(duì)象夹供。下一章節(jié)我們會(huì)討論這種方法灵份。
推薦:在持久化Fragment中管理對(duì)象
從Android 3.0開(kāi)始引入Fragment的概念,在Activity中持久化對(duì)象的方法哮洽,是通過(guò)持久化Fragment包裝和管理這些對(duì)象填渠。默認(rèn)情況下,在配置發(fā)生改變時(shí)Fragment的重構(gòu)是跟隨父Activity的鸟辅。通過(guò)調(diào)用Fragment#setRetainInstance(true)
,跳過(guò)銷毀重構(gòu)的過(guò)程氛什,告訴系統(tǒng)在Acitivity重構(gòu)時(shí)保持當(dāng)前Fragment實(shí)例的狀態(tài)。這在我們運(yùn)行Thread
,AsyncTask
,Socket
匪凉,使用持久化Fragment就變得相當(dāng)有利枪眉。
下面的樣例代碼示范,在配置改變的情況下再层,怎么去使用持久化Fragment來(lái)保存AsyncTask贸铜。代碼保證了進(jìn)度更新和正確傳遞結(jié)果到Activity,在配置改變時(shí)不會(huì)泄露AsyncTask的引用。
代碼包括兩個(gè)類树绩,第一個(gè)是MainActivity
* This Activity displays the screen's UI, creates a TaskFragment
* to manage the task, and receives progress updates and results
* from the TaskFragment when they occur.
*/
public class MainActivity extends Activity implements TaskFragment.TaskCallbacks {
private static final String TAG_TASK_FRAGMENT = "task_fragment";
private TaskFragment mTaskFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager fm = getFragmentManager();
mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT);
// If the Fragment is non-null, then it is currently being
// retained across a configuration change.
if (mTaskFragment == null) {
mTaskFragment = new TaskFragment();
fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit();
}
// TODO: initialize views, restore saved state, etc.
}
// The four methods below are called by the TaskFragment when new
// progress updates or results are available. The MainActivity
// should respond by updating its UI to indicate the change.
@Override
public void onPreExecute() { ... }
@Override
public void onProgressUpdate(int percent) { ... }
@Override
public void onCancelled() { ... }
@Override
public void onPostExecute() { ... }
}
還有TaskFragment
* This Fragment manages a single background task and retains
* itself across configuration changes.
*/
public class TaskFragment extends Fragment {
/**
* Callback interface through which the fragment will report the
* task's progress and results back to the Activity.
*/
interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(int percent);
void onCancelled();
void onPostExecute();
}
private TaskCallbacks mCallbacks;
private DummyTask mTask;
/**
* Hold a reference to the parent Activity so we can report the
* task's current progress and results. The Android framework
* will pass us a reference to the newly created Activity after
* each configuration change.
*/
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (TaskCallbacks) activity;
}
/**
* This method will only be called once when the retained
* Fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
// Create and execute the background task.
mTask = new DummyTask();
mTask.execute();
}
/**
* Set the callback to null so we don't accidentally leak the
* Activity instance.
*/
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
/**
* A dummy task that performs some (dumb) background work and
* proxies progress updates and results back to the Activity.
*
* Note that we need to check if the callbacks are null in each
* method in case they are invoked after the Activity's and
* Fragment's onDestroy() method have been called.
*/
private class DummyTask extends AsyncTask<Void, Integer, Void> {
@Override
protected void onPreExecute() {
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
}
/**
* Note that we do NOT call the callback object's methods
* directly from the background thread, as this could result
* in a race condition.
*/
@Override
protected Void doInBackground(Void... ignore) {
for (int i = 0; !isCancelled() && i < 100; i++) {
SystemClock.sleep(100);
publishProgress(i);
}
return null;
}
@Override
protected void onProgressUpdate(Integer... percent) {
if (mCallbacks != null) {
mCallbacks.onProgressUpdate(percent[0]);
}
}
@Override
protected void onCancelled() {
if (mCallbacks != null) {
mCallbacks.onCancelled();
}
}
@Override
protected void onPostExecute(Void ignore) {
if (mCallbacks != null) {
mCallbacks.onPostExecute();
}
}
}
}
事件流
當(dāng)MainActivity
第一次啟動(dòng)時(shí),實(shí)例化同時(shí)添加TaskFragment
到Activity隐轩。TaskFragment
創(chuàng)建并執(zhí)行AsyncTask
,將更新結(jié)果傳遞回MainActivity
通過(guò)TaskCallbacks
接口饺饭。
當(dāng)配置發(fā)生改變時(shí),MainActivity
正常走生命周期的重構(gòu)方法职车,一旦新的Activity創(chuàng)建成功后會(huì)回調(diào)Fragmentd的onAttach(Activity)
方法瘫俊,即使在配置改變的情況下鹊杖,保證Fragment當(dāng)前持有的是最新的Activity的引用。
代碼運(yùn)行的結(jié)果是簡(jiǎn)單且可靠的扛芽;應(yīng)用程序框架會(huì)處理Activity重建后的實(shí)例骂蓖,TaskFragment
和AsyncTask
無(wú)需關(guān)注配置的改變。onPostExecute()
可以在onDetach()
和onAttach()
方法回調(diào)之間執(zhí)行川尖。
參考在StackOverFlow上的回答和在Google+回答Doug Stevenson的問(wèn)題登下。
結(jié)論
與Activity生命周期相關(guān)的同步后臺(tái)任務(wù)的處理是很有技巧的,配置改變也容易令人迷惑叮喳。幸運(yùn)的是被芳,通過(guò)長(zhǎng)期持有父Activity的引用,即使在被重構(gòu)的情況下馍悟,持久化Fragment使得這些事件的處理變得簡(jiǎn)單畔濒。
你可以在Play Store上下載到代碼,源碼在github上開(kāi)源了锣咒,下載侵状,import到Eclipse,隨心所欲地改吧;)
譯者注
屏幕旋轉(zhuǎn)總結(jié)
- 不設(shè)置Activity的
android:configChanges
時(shí),切屏?xí)匦抡{(diào)用各個(gè)生命周期毅整,切橫屏?xí)r會(huì)執(zhí)行一次趣兄,切豎屏?xí)r會(huì)執(zhí)行兩次 - 設(shè)置Activity的
android:configChanges="orientation"
時(shí),切屏還是會(huì)重新調(diào)用各個(gè)生命周期毛嫉,切橫诽俯、豎屏?xí)r只會(huì)執(zhí)行一次 - 設(shè)置Activity的
android:configChanges="orientation|keyboardHidden"
時(shí),切屏不會(huì)重新調(diào)用各個(gè)生命周期承粤,只會(huì)執(zhí)行onConfigurationChanged
方法
意見(jiàn)修改
- 歡迎指出翻譯有誤的地方