4.手動處理旋轉
4.1 問題
在旋轉過程中,預設會將Activity銷燬,然後再重新建立,這會嚴重影響應用程式的效能。
如果沒有自行修改的話,在配置變化時,Android會結束當前的Activity例項,然後重新建立一個適用於新配置的Activity例項,這會帶來效能上的損失,因為這需要先儲存UI狀態,然後再完全重新構建UI。
4.2 解決方案
(API Level 1)
利用清單檔案中的android:configChanges引數,告訴Android某個Activity在處理旋轉事件時不需要執行時進行干預,這不僅能降低Android的工作量,即銷燬和重建Activity例項,也會降低應用程式的工作量。保持Activity例項不變,應用程式就不必為了針對使用者維持一致性而花費時間儲存和還原應用程式的當前狀態。
註冊了一個或多個配置變動的Activity可以通過Activity.onConfigurationChanged()回撥方法收到通知,在該方法中可以執行各種跟配置變動相關的必要手動操作。
要完全以手動方式處理旋轉,Activity至少要註冊三個配置變動引數:orientation、keyboardHidden和screenSize。
- orientation : 為裝置方向變動的事件註冊Activity。
- screenSize : 為裝置螢幕長寬比變動的事件註冊Activity。在每次方向變動時才會發生此事件。
- keyboardHidden : 為使用者滑開或關閉物理按鍵的事件註冊Activity。
儘管後者看上去跟螢幕旋轉沒有直接關係,但如果不註冊這些事件的話,Android就會在這些事件發生時重建Activity,這會使前面手動處理旋轉的努力付之東流。
4.3 實現機制
將這些引數新增到AndroidManifest.xml的任意一個<activity>元素中,如下所示:
<activity android:name = ".MyActivity" android:configChanges = "orientation|keyboardHidden|screenSize"/>
在一條賦值語句中可以註冊多種變動,用“|”符號將它們分開即可。因為這些引數都不能應用於<application>元素,所以每個Activity都必須在AndroidManifest.xml中註冊。
註冊Activity後,配置發生改變時就會呼叫Activity的onConfigurationChanged()方法。以下三段程式碼到一個簡潔的Activity定義,用於處理變動發生時產生的回撥。
res/layout/activity_manual.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Rotate the device, Activity will remain"/> <CheckBox android:id="@+id/override" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Check to Force Reload View"/> <EditText android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
res/layout-land/activity_manual.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <CheckBox android:id="@+id/override" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Check to Force Reload View"/> <EditText android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
手動管理旋轉的Activity
public class MyActivity extends Activity { //引用檢視元素 private EditText mEditText; private CheckBox mCheckBox; @Override protected void onCreate(Bundle savedInstanceState) { //必須呼叫超類 super.onCreate(savedInstanceState); //載入檢視資源 loadView(); } @Override public void onConfigurationChanged(Configuration newConfig) { //必須呼叫超類 super.onConfigurationChanged(newConfig); //如果選中此複選框,則只重新載入新配置下的元素 if (mCheckBox.isChecked()) { final Bundle uiState = new Bundle(); //儲存重要的UI狀態 saveState(uiState); //重新載入檢視資源 loadView(); //恢復UI狀態 restoreState(uiState); } } //實現持久化UI狀態的程式碼 private void saveState(Bundle state) { state.putBoolean("checkbox", mCheckBox.isChecked()); state.putString("text", mEditText.getText().toString()); } //恢復重新載入之前儲存的任何元素 private void restoreState(Bundle state) { mCheckBox.setChecked(state.getBoolean("checkbox")); mEditText.setText(state.getString("text")); } //設定內容檢視並獲得檢視引用 private void loadView() { setContentView(R.layout.activity_manual); //我們必須在設定新的佈局時重新檢視引用 mCheckBox = (CheckBox) findViewById(R.id.override); mEditText = (EditText) findViewById(R.id.text); } }
注意:
Google並不推薦這樣處理旋轉,除非應用程式的效能能確實有此要求。在響應各個變動事件時,所有跟配置有關的資源都必須手動載入。
Google建議允許預設的Activity旋轉行為重建,除非應用程式的效能能確實有此要求。這主要是因為,阻止預設的Activity重建會導致Android無法載入在資源條件目錄中存放的備選資源(例如res/layout-land/目錄中的橫屏佈局資源)。
在例項的Activity中,所有用於處理檢視佈局的程式碼都被抽象成在onCreate()和onConfigurationChanged()中呼叫的私有方法loadView()中。在該方法中,類似setContentView()這樣的程式碼可以確保載入與配置相匹配的佈局。
呼叫setContentView()會重新載入檢視,所以一定要在沒有諸如onSaveInstanceState()和onRestoreInstanceState()等生命週期回撥函式的協助下,儲存所有的UI狀態。為了達到該目的,示例中實現了saveState()和restoreState()方法。
為了在不新增檢視過載程式碼的情況下演示此人Activity的行為,我們在佈局中關聯了一個複選框,用於確定在配置改變時檢視是否再次載入,選中此複選框時,Activity仍然會旋轉在新的方向重繪其內容。然而,相反的配置佈局(橫向或縱向)將不會重新載入。