当android app发生屏幕旋转、键盘可用性变化及用户启用多窗口模式时,正在运行的Activity会被重启,即先调用onDestroy()
,再调用 onCreate()
方法。
注意:屏幕方向、键盘可用性,以及当用户启用多窗口模式这些称为设备配置,它们一开始就被配置好的。
重启的原因是为了可以使用新的资源来匹配新的设备配置。比如由竖屏旋转为横屏时,可能要使用不同的布局。这个重启为这些改变提供了一个机会。我们就可以借此机会来利用与新设备配置相匹配的备用资源来自动重新加载我们的应用,从而帮助应用适应新配置。
因为设备配置发生变化时,当前Activity会重启,那么有些数据就可能会丢失,比如说用户输入的内容,因为那么输入框在activity重启时,都会被初始化为空。我们肯定是不希望丢失的。所以当发生配置变化时,我们如何做才能不丢失数据呢?这些数据我们通常都喜欢称其为activity状态(想想也确实如此,状态就是要由数据来表现的)。
针对设备变化的处理有两种处理方式:
如果重启activity需要恢复大量数据、重新建立网络连接等等,那么配置变化引起的activity重启会严重影响用户体验,给用户 感觉app的运行很慢。
如果应用在设备配置变化时,不需要更新资源,并且考到虑性能的限制,尽量避免activity重启,那么我们可以声明 Activity 自行处理配置变更,从而阻止系统重启 Activity。
在AndroidManifest.xml清单文件中编辑相应的
元素,增加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 生命周期中的状态。因为一些其他配置变更会强制重启应用,而且某些事件需要我们进行处理,例如:用户想离开应用,而系统在此之前便已销毁了该应用。
如果我们没有采用上面的措施阻止activity重启,那么我们可以通过以下方式来应对配置变化的情况。
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的状态。
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() {
// @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;
}
}
可以借助Sqlite数据库或本地文件来实现,甚至可以将数据上传到网络上。先将数据存储到本地,再
ViewModel对象以及持久存储,以在配置变更时保存并恢复 Activity 的界面状态。