Android官方文件—APP資源(Handling Runtime Changes)
處理執行時更改
某些裝置配置可能會在執行時更改(例如螢幕方向,鍵盤可用性和語言)。當發生這樣的更改時,Android會重新啟動正在執行的Activity(呼叫onDestroy(),然後呼叫onCreate())。重新啟動行為旨在通過使用與新裝置配置匹配的備用資源自動重新載入應用程式,幫助您的應用程式適應新配置。
要正確處理重新啟動,您的活動必須通過正常的Activity生命週期恢復其先前的狀態,在此生命週期中Android會在銷燬活動之前呼叫onSaveInstanceState(),以便您可以儲存有關應用程式狀態的資料。然後,您可以在onCreate()或onRestoreInstanceState()期間恢復狀態。
要測試應用程式在應用程式狀態不變的情況下重新啟動自身,您應該在應用程式中執行各種任務時呼叫配置更改(例如更改螢幕方向)。您的應用程式應該能夠在不丟失使用者資料或狀態的情況下隨時重新啟動,以便處理諸如配置更改之類的事件,或者當用戶收到來電時,然後在您的申請過程之後很久就返回到您的應用程式銷燬。要了解如何恢復活動狀態,請閱讀活動生命週期。
但是,您可能會遇到這樣的情況:重新啟動應用程式並恢復大量資料可能成本高昂並且會導致糟糕的使用者體驗。在這種情況下,您還有兩個選擇:
- 在配置更改期間保留物件
允許您的活動在配置更改時重新啟動,但將有狀態物件帶到活動的新例項。
- 自己處理配置更改
在某些配置更改期間阻止系統重新啟動您的活動,但在配置更改時接收回調,以便您可以根據需要手動更新活動。
在配置更改期間保留物件
如果重新啟動活動需要恢復大量資料,重新建立網路連線或執行其他密集操作,則由於配置更改而導致的完全重新啟動可能會降低使用者體驗。此外,您可能無法使用系統為onSaveInstanceState()回撥為您儲存的Bundle完全恢復活動狀態 - 它不是為承載大型物件(如點陣圖)而設計的,其中的資料必須序列化然後反序列化,這可能會消耗大量記憶體並使配置變化緩慢。在這種情況下,您可以通過在配置更改時重新啟動活動時保留Fragment來減輕重新初始化活動的負擔。此片段可以包含對要保留的有狀態物件的引用。
當Android系統因配置更改而關閉您的活動時,您標記為要保留的活動片段不會被銷燬。您可以將此類片段新增到活動中以保留有狀態物件。
- 擴充套件Fragment類並宣告對有狀態物件的引用。
- 建立片段時呼叫setRetainInstance(boolean)。
- 將片段新增到您的活動中。
- 重新啟動活動時,使用FragmentManager檢索片段。
例如,按如下方式定義片段:
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
警告:雖然可以儲存任何物件,但是永遠不應傳遞與Activity關聯的物件,例如Drawable,Adapter,View或與Context關聯的任何其他物件。如果這樣做,它將洩漏原始活動例項的所有檢視和資源。 (資源洩漏意味著您的應用程式保持對它們的保持並且它們不能被垃圾收集,因此可能會丟失大量記憶體。)
然後使用FragmentManager將片段新增到活動中。在執行時配置更改期間,當活動再次啟動時,您可以從片段中獲取資料物件。例如,按如下方式定義您的活動:
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
在此示例中,onCreate()新增片段或恢復對它的引用。 onCreate()還將有狀態物件儲存在片段例項中。 onDestroy()更新保留的片段例項中的有狀態物件。
自行處理配置改變
如果您的應用程式在特定配置更改期間不需要更新資源,並且您有效能限制要求您避免重新啟動活動,那麼您可以宣告您的活動自己處理配置更改,這會阻止系統重新啟動活動。
注意:自行處理配置更改會使使用備用資源變得更加困難,因為系統不會自動為您應用它們。當您必須避免由於配置更改而重新啟動時,此技術應被視為最後的手段,並且不建議用於大多數應用程式。
要宣告您的活動處理配置更改,請編輯清單檔案中相應的<activity>元素,以包含android:configChanges屬性,其中包含表示要處理的配置的值。 android:configChanges屬性的文件中列出了可能的值(最常用的值是“orientation”以防止在螢幕方向更改時重新啟動,“keyboardHidden”以防止在鍵盤可用性更改時重新啟動)。您可以通過用管道分隔屬性來宣告屬性中的多個配置值字元。
例如,以下清單程式碼聲明瞭一個處理螢幕方向更改和鍵盤可用性更改的活動:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
現在,當其中一個配置發生更改時,MyActivity不會重新啟動。相反,MyActivity接收對onConfigurationChanged()的呼叫。此方法傳遞一個Configuration物件,該物件指定新裝置配置。通過讀取“配置”中的欄位,您可以確定新配置並通過更新介面中使用的資源進行適當的更改。在呼叫此方法時,您的活動的Resources物件將更新為基於新配置返回資源,因此您可以輕鬆重置UI的元素,而無需系統重新啟動您的活動。
警告:從Android 3.2(API級別13)開始,當裝置在縱向和橫向之間切換時,“螢幕大小”也會更改。因此,如果要在開發API級別13或更高級別(由minSdkVersion和targetSdkVersion屬性宣告)時由於方向更改而阻止執行時重新啟動,則除了“orientation”值之外,還必須包含“screenSize”值。也就是說,你必須decalare android:configChanges =“orientation | screenSize”。但是,如果您的應用程式的目標是API級別12或更低,那麼您的活動始終會自行處理此配置更改(即使在Android 3.2或更高版本的裝置上執行,此配置更改也不會重新啟動您的活動)。
例如,以下onConfigurationChanged()實現檢查當前裝置方向:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
Configuration物件表示所有當前配置,而不僅僅是已更改的配置。大多數情況下,您不會完全關心配置的更改方式,只需重新分配所有資源,這些資源可提供您正在處理的配置的替代方案。例如,因為現在更新了Resources物件,所以可以使用setImageResource()重置任何ImageView,並使用適當的新配置資源(如提供資源中所述)。
請記住:當您宣告活動以處理配置更改時,您有責任重置您提供備選方案的任何元素。如果宣告活動以處理方向更改並且影象應在橫向和縱向之間更改,則必須在onConfigurationChanged()期間將每個資源重新分配給每個元素。
如果您不需要根據這些配置更改來更新應用程式,則可以不實現onConfigurationChanged()。在這種情況下,仍然使用配置更改之前使用的所有資源,並且您只避免重新啟動活動。但是,您的應用程式應該始終能夠關閉並重新啟動其先前的狀態,因此您不應該認為這種技術可以避免在正常活動生命週期中保留您的狀態。這不僅是因為還有其他配置更改無法阻止重新啟動應用程式,還因為您應該處理事件,例如使用者離開應用程式時它會在使用者返回之前被銷燬。
有關您可以在活動中處理哪些配置更改的更多資訊,請參閱android:configChanges文件和Configuration類。