橫屏豎屏的切換就是最為常見的配置變化,我們在Pro Android學習筆記(四一):Fragment(6):資料保留中讨論過。 配置變化還有裝置連接配接了dock,改變語言等,在資源res/中,不同的配置有不同的字尾來進行最佳比對,具體見Pro Android學習筆記(四):了解Android資源(下)的資源和配置的變更 ,我們從中可以看到Android檢測哪些配置變化。
當配置變化時,目前的activity被destroy,并新re-created一個activity對象,我們要確定使用者體驗的無縫切換。我們應該盡量避免re-create的時間,減少activity處理的非UI部分。在配置變化是activity被destroy,但是application仍然存在,背景線程,資料庫、content provider也存在,是以盡可能将資料和業務邏輯放在activity之外。
Activity的destory/create過程
配置變化過程中,Activity經曆destroy,重新create的過程,有三個回調函數需要關注。
onSaveInstanceState(Bundle outState):當檢測到配置變化時會觸發此回調函數,在activity的onStop()之前被調用,在此通過Bundle儲存資料,并傳遞到新建立activity的onCreate(Bundle savedInstanceState)和onRestoreInstanceState(Bundle savedInstanceState)的參數中。對于帶有android:id的view,系統在onSaveInstanceState()中或自動存儲使用者輸入的内容,并在重建時恢複。是以,如果我們需要重寫,要記住調用super.onSaveInstanceState(); 通過bundle儲存資料,例如整數使用putInt()方法,對于組合對象,可以采用putParcelable()
onCreate()和onRestoreInstanceState()都可以通過bundle擷取資訊。一般會在onCreate()中處理,因為那裡進行UI初始化的處理。但是有時我們會extends activity,這是在onRestoreInstanceState()中處理有時會更為簡單。相關的執行順序為onCreate() –>onStart() –>onRestoreInstanceState() –> onResume()。
如果我們通過bundle進行資料保留,要注意,這種情況下,舊的activity是不能進行垃圾回收的,存在記憶體洩漏的風險,是以我們應當避免将activity上下文相關的Drawables、Views、Adapters等對象放入bundle中。如果實作需要,我們可以将之放在activity之外,或者使用某些id索引(int這類不是保持對象,而是指派)。
Fragment的destory/create過程
onSaveInstanceState():
Fragment和activity一樣,可以通過onSaveInstanceState()進行資料儲存,可以通過onInflate()、onCreate()、onCreateView()和onActivityCreate()進行擷取。Android隻保證在onDestroy()之前調用onSaveInstanceState(),不保證具體的執行順序,是以有可能在調用onSaveInstanceState()時,相關的view容器已經無效,也就是,不應該在onSaveInstanceState()中處理view的資料。有例如,如果fragment在back stack,由于不可視,沒有view(即為null),這并非異常,在代碼中需要注意。同樣,不應将fragment重建立後已不存在的對象進行保留,bundle攜帶的資料應盡可能地少,減少記憶體洩漏的風險。
saveFragmentInstanceState():
如果确實需要進行view的處理,可以通過fragment管理器主導要求觸發onSaveInstanceState(),如下:
getFragmentManager().saveFragmentInstanceState(this);
saveFragmentInstanceState()将傳回Fragment.SavedState對象,這state對象中包含fragment的狀态(含有在onSaveInstanceState中儲存的bundle),通過setInitialSavedState()可以在新的fragment恢複。我們利用Dialog中的提示框的例子,對例子進行少許修改,實作在下次彈出提示框時,能保留上次的輸入值。
public class PromptDialogFragment extends DialogFragment implements OnClickListener{
… …
private static Fragment.SavedState fgState = null; //可以儲存在Application的某個對象,為了示範友善,本例子采用靜态函數
public static PromptDialogFragment newInstance(String prompt){
PromptDialogFragment pdf = new PromptDialogFragment();
pdf.setInitialSavedState(fgState); //擷取儲存的狀态
... ...
return pdf;
}
@Override //如果fgState非null,那麼就可以從得到Bundle資料。在原例子中,新彈框是建立的dialog fragment,和原對話框無關,也不屬于配置改變,是以bundle為null。在新例子中,由于我們儲存了上一次對話框的state,并在執行個體建立時恢複,是以可以擷取原對話框的state,包括當中的bundle
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
... ...
if(savedInstanceState != null){
CharSequence text = savedInstanceState.getCharSequence("input");
et.setText(text == null ? "" : text);
}
//将原來dismiss的button改為觸發資訊儲存
Button dismissBtn = (Button)v.findViewById(R.id.button_dismiss);
dismissBtn.setOnClickListener(this);
dismissBtn.setText("儲存資訊");
......
}
@Override
public void onSaveInstanceState(Bundle outState) {
showInfo("onSaveInstanceState() is called");
outState.putCharSequence("input", et.getText());
super.onSaveInstanceState(outState);
}
@Override
public void onClick(View v) {
//saveFragmentInstanceState()将觸發onSaveInstanceState()回調函數,進行資訊儲存,并将儲存内容存放在fgState對象中
switch(v.getId()){
case R.id.button_dismiss: //新例子為“資訊儲存”觸發按鈕
fgState = getFragmentManager().saveFragmentInstanceState(this);
break;
... ...
}
}
}
onRetainInstance():
通過onRetainInstatnce(true),當activity被destroy和re-created時,fragment的onDestroy()并不會被調用,說明fragment對象仍然存在于app中,在新activity建立時,fragment對象将attach到新的activity中,這個過程無需調用onCreate(),因為并重新創新fragment。我們在原對話框的例子進行測試,在onCreate()中加入onRetainInstatnce(true),進行橫豎屏切換,狀态跟蹤如下。
相關連結: 我的Android開發相關文章