當android app發生屏幕旋轉、鍵盤可用性變化及用戶啓用多窗口模式時,正在運行的Activity會被重啓,即先調用onDestroy()
,再調用 onCreate()
方法。
注意:屏幕方向、鍵盤可用性,以及當用戶啓用多窗口模式這些稱爲設備配置,它們一開始就被配置好的。
重啓的原因是爲了可以使用新的資源來匹配新的設備配置。比如由豎屏旋轉爲橫屏時,可能要使用不同的佈局。這個重啓爲這些改變提供了一個機會。我們就可以藉此機會來利用與新設備配置相匹配的備用資源來自動重新加載我們的應用,從而幫助應用適應新配置。
因爲設備配置發生變化時,當前Activity會重啓,那麼有些數據就可能會丟失,比如說用戶輸入的內容,因爲那麼輸入框在activity重啓時,都會被初始化爲空。我們肯定是不希望丟失的。所以當發生配置變化時,我們如何做才能不丟失數據呢?這些數據我們通常都喜歡稱其爲activity狀態(想想也確實如此,狀態就是要由數據來表現的)。
針對設備變化的處理有兩種處理方式:
1.自行處理設備配置變化
如果重啓activity需要恢復大量數據、重新建立網絡連接等等,那麼配置變化引起的activity重啓會嚴重影響用戶體驗,給用戶 感覺app的運行很慢。
如果應用在設備配置變化時,不需要更新資源,並且考到慮性能的限制,儘量避免activity重啓,那麼我們可以聲明 Activity 自行處理配置變更,從而阻止系統重啓 Activity。
1.1.聲明由Activity 處理配置變更的方法
在AndroidManifest.xml清單文件中編輯相應的 <Activity>
元素,增加android:configChanges
屬性,該屬性的值表示要處理的配置。阻止系統在特定配置變更期間重啓您的 Activity。該屬性最常用的值包括 "orientation"
、"screenSize"
、 "keyboardHidden"
。可以在屬性中聲明多個配置值,方法是用管道 |
字符將其進行分隔。
-
"orientation"
:在屏幕方向發生變更時阻止重啓。 -
"screenSize"
:在屏幕方向發生變更時阻止重啓,但僅適用於 Android 3.2(API 級別 13)及以上版本的系統。 -
"keyboardHidden"
:在鍵盤可用性發生變更時阻止重啓。
例如,聲明 Activity 可同時處理屏幕方向變更和鍵盤可用性變更:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
配置變更時,應用會收到回調,以便您可以根據需要手動更新 Activity。如上面配置後,即便其中某個配置發生變化,MyActivity
也不會重啓。但 MyActivity
會接收到對 onConfigurationChanged() 的調用消息。此方法會收到傳遞的 Configuration
對象,從而指定新設備配置。您可以通過讀取 Configuration
中的字段確定新配置,然後通過更新界面所用資源進行適當的更改。調用此方法時,Activity 的 Resources
對象會相應地進行更新,並根據新配置返回資源,以便您在系統不重啓 Activity 的情況下輕鬆重置界面元素。例如,以下 onConfigurationChanged()
實現用於檢查當前的設備方向:
注意:**只有activity配置了android:configChanges
,當配置變化時,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
對象代表所有當前配置,而不僅僅是已變更的配置。如果無需根據這些配置變更更新應用,則可不必實現 onConfigurationChanged()。在此情況下,應用仍會使用配置變更前所用的全部資源,區別在於我們無需重啓 Activity。但是,不應該認爲,使用此方法即可無需保留正常 Activity 生命週期中的狀態。因爲一些其他配置變更會強制重啓應用,而且某些事件需要我們進行處理,例如:用戶想離開應用,而系統在此之前便已銷燬了該應用。
2.在設備配置變化期間保存數據
如果我們沒有采用上面的措施阻止activity重啓,那麼我們可以通過以下方式來應對配置變化的情況。
2.1. onSaveInstanceState()
activity重啓會經歷 onDestroy()
和 onCreate()
,在activity銷燬時,會調用 onSaveInstanceState()
方法。所以我們可以將activity的狀態(數據)通過 onSaveInstanceState()
保存起來,然後在 onCreate()
或 onRestoreInstanceState()
中進行恢復。
private EditText mET;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this,"H工",Toast.LENGTH_LONG).show();
mET = findViewById(R.id.et);
// if(savedInstanceState != null && savedInstanceState.containsKey("et")){
// mET.setText(savedInstanceState.getString("et"));
// }
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Toast.makeText(this,"newo",Toast.LENGTH_LONG).show();
outState.putString("et",mET.getText().toString());
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if(savedInstanceState != null && savedInstanceState.containsKey("et")){
mET.setText(savedInstanceState.getString("et"));
}
}
使用onSaveInstanceState()
使用Bundle類來保存數據,但是該類並非用於攜帶大型對象(例如位圖),且其中的數據必須依次在主線程中進行序列化和反序列化,可能會消耗大量內存並降低配置變更的速度。
因此可以考慮使用ViewModel來保存activity的狀態。
2.2.ViewModel
ViewModel負責爲Activity或Fragment準備和管理數據。ViewModel的作用就是爲Activity或Fragment獲取或保存必要的信息。它也可以用來處理activity或fragment
與應用其他部分的通信。ViewModel的創建始終與作用域(Activity/Fragment)相關聯,只要作用域處於活動狀態,模型就將保留。例如,作用域是Activity,直接到Activity執行finish(不是onDestroy喔),ViewModel纔會被銷燬,否則將一直保留。也就是說當設備配置變化時,ViewModel的所有者(Activity或Fragment)實例被銷燬時,ViewModel是不會被銷燬的。ViewModel的所有者(Activity或Fragment)的新實例會重新連接上這個ViewModel。
Activity和Fragment應能夠觀察到ViewModel的變化。因此ViewModel通常通過LiveData(或Android Data Binding)來暴露它的信息。ViewModel只負責爲UI管理數據。它絕不應訪問視圖層次結構或保留對"Activity"或"Fragment"的引用。
因爲Activity在配置發生改變時,會先調用onDestroy()
再調用onCreate()
方法,那麼我們可以在onDestroy()
或onSaveInstanceState()
將數據保存到ViewModel,然後在onCreate()
或onRestoreInstanceState()
方法中進行恢復:
public class MainActivity extends AppCompatActivity {
private EditText mET;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mET = findViewById(R.id.et);
MainModel mainModel = new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(MainModel.class);
// mainModel.getDataBean().observe(this, new Observer<DataBean>() {
// @Override
// public void onChanged(DataBean dataBean) {
// mET.setText(dataBean.getData());
// }
// });
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
MainModel mainModel = new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(MainModel.class);
mainModel.getDataBean().observe(this, new Observer<DataBean>() {
@Override
public void onChanged(DataBean dataBean) {
mET.setText(dataBean.getData());
}
});
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
MainModel mainModel = new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(MainModel.class);
mainModel.doAction(mET.getText().toString());
}
@Override
protected void onDestroy() {
super.onDestroy();
// MainModel mainModel = new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(MainModel.class);
// mainModel.doAction(mET.getText().toString());
}
}
MainModel的定義:
public class MainModel extends ViewModel {
private final MutableLiveData<DataBean> dataBeanLiveData = new MutableLiveData<>();
public LiveData<DataBean> getDataBean(){
return dataBeanLiveData;
}
public MainModel(){
// trigger databean load
dataBeanLiveData.setValue(new DataBean());
}
void doAction(String data){
// depending on the action, do necessary business logic calls and update the
// userLiveData.
dataBeanLiveData.getValue().setData(data);
}
}
DataBean的定義:
public class DataBean {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
3.持久存儲
可以藉助Sqlite數據庫或本地文件來實現,甚至可以將數據上傳到網絡上。先將數據存儲到本地,再
ViewModel對象以及持久存儲,以在配置變更時保存並恢復 Activity 的界面狀態。