一个Activity从创建到退出,正常的生命周期流程是:
onCreate-->onStart-->onResume-->onPause-->onStop-->onDestroy
当屏幕旋转时,Android系统会自动将当前屏幕方向Activity销毁,再重新创建一个适应新屏幕方向的Activity。
很自然的,我们能猜到会执行onPause-->onStop-->onDestroy-->onCreate-->onStart-->onResume
这些方法。但除了这些Android还为我们考虑到了数据丢失的问题。
试想一下这种场景:页面中有一个TextView和一个Button,每点击一次Button,TextView显示点击的次数。当点击了一些次数后,旋转屏幕,你会发现TextView上记录的次数恢复到了最开始的状态。
原因也不难解释:因为整个Activity销毁了,重新创建的Activity是一个最开始的状态。
为了处理这种由于销毁重建导致的页面数据丢失的问题,Android提供了另外两个方法 onSaveInstanceState 和 onRestoreInstanceState,算是对生命周期方法的补充吧。
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "onSaveInstanceState: ");
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG, "onRestoreInstanceState: ");
}
注意:onSaveInstanceState有两个重载方法,带outPersistentState参数的是针对那种重启手机的情况的,本文我们暂时不考虑,也不会回调。我们只关注第二个不带outPersistentState参数的就行了。
@Override
public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
}@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);}
从名字上也可以看出,onSaveInstanceState是对状态的保存,onRestoreInstanceState是对状态的恢复。
了解到这些,当屏幕旋转后回调的方法最终是这样的:
onPause-->onStop-->onSaveInstanceState-->onDestroy-->onCreate-->
onStart-->onRestoreInstanceState-->onResume
没错,onSaveInstanceState在onDestroy之前;onRestoreInstanceState在onResume之前。
除此之外,我们还应了解到,我们最常见的onCreate方法中其实有一个参数savedInstanceState。一直以来很少用到它,但现在终于轮到它发挥作用了。它就是之前销毁Activity时保存的数据,这样以来其实不用等到onRestoreInstanceState方法回调,在onCreate时我们就能拿到之前保存的数据了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_config_change_test);
if (savedInstanceState != null) {
// 页面重建了
Log.d("TAG", "onCreate: savedInstanceState:" + savedInstanceState);
}
}
再仔细观察下,savedInstanceState、outState这些参数都是Bundle类型的哦,也就是说他们互相传递毫无障碍,都是用来盛放键值对儿的。
经过测试,以下情况都会调用onSaveInstanceState方法
而正常的退出Activity是不会调用onSaveInstanceState的
可见,onSaveInstanceState 就是在那些用户期望保留数据的场景才会调用,这也是它存在的初衷。
除了Activity,我们还应该了解到Fragment在屏幕旋转过程也会自动销毁重建。看过Fragment的基础与应用系列文章的伙伴都知道,Fragment是依附在Activity上的,可以说是一些视图控件View组成的“片段”页面。当Activity都销毁了,那它上面的View肯定也都销毁了。相应的,Activity的View树恢复了,它自然也得把上面的Fragment恢复。在Activity看来,Fragment就是它上面的一些View构成的集合而已,当然要一视同仁的恢复了。
这里直接给出依次调用的生命周期方法。文末会给出源码,你也可以自己写小例子测试。
Fragment: onPause:
TestActivity: onPause:
Fragment: onStop:
TestActivity: onStop:
Fragment: onSaveInstanceState:
TestActivity: onSaveInstanceState:
Fragment: onDestroyView:
Fragment: onDestroy:
Fragment: onDetach:
TestActivity: onDestroy:
Fragment: Fragment 构造方法:
Fragment: onAttach:
Fragment: onCreate: savedInstanceState:非空
TestActivity: onCreate: savedInstanceState:非空
Fragment: onCreateView:
Fragment: onViewCreated:
Fragment: onActivityCreated:
Fragment: onViewStateRestored:
Fragment: onStart:
TestActivity: onStart:
TestActivity: onRestoreInstanceState:
TestActivity: onResume:
Fragment: onResume:
可见,在Activity销毁重建的同时,其上的Fragment也进行了类似的过程,也存在保存和恢复数据的方法:
Fragment: onSaveInstanceState:
Fragment: onViewStateRestored:
那么屏幕旋转后一定非要页面重建吗?当然不是,Android尽可能的为我们提供了选择。
如果你不想让页面的Activity销毁重建的话,可以在AndroidManifest.xml文件的Activity节点里添加android:configChanges
配置,如下:
<activity
android:name=".config_change.ConfigChangeTestActivity"
android:configChanges="orientation|screenSize" />
android:configChanges
的值代表了哪些配置发生变化时页面不必重建。上述配置代码的orientation|screenSize
意思是说,方向 | 屏幕大小 发生变化时页面不重建。
注意:经过本人测试,这里必须同时配置
orientation|screenSize
这两个值才能阻止页面重建,只配置一个orientation或者screenSize都是不行的。
另外,还有一些其他值:screenLayout|keyboardHidden等,有兴趣可以自行了解。
配置完android:configChanges后,旋转屏幕你会发现,虽然页面旋转了,但Activity的生命周期方法没有调用,也就是页面没有销毁重建。目的达到了,但真的万事大吉了吗?
事实上,Android官方是不推荐我们添加这个阻止自动重建的配置的。仔细想想,为什么它会设计成默认自动重建,就是因为在屏幕方向或者大小发生变化时,页面所依赖的尺寸等值也不一样了。如果不重建,就意味着你可能会将竖屏时候的值应用给旋转后的横屏Activity,这样很难说不会出问题。除非你自己完成屏幕旋转后的适配工作,而这个是比较难以考虑周全的。
但Android依然把这个选择留给了我们,只是提醒我们要小心使用。当配置了android:configChanges阻止了页面重建后,意味着我们要自己处理配置变化后的适配工作。这时屏幕旋转,会调用onConfigurationChanged
方法。
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d("TAG", "onConfigurationChanged: landscape");
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d("TAG", "onConfigurationChanged: portrait");
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
可以看出,配置数据存放在参数newConfig中,我们可以从中获取到新的屏幕方向等配置信息,进而做适配我们页面的处理。
最后贴出本文用到的案例源码,以供参考
主界面ConfigChangeTestActivity.java
public class ConfigChangeTestActivity extends AppCompatActivity {
private static final String TAG = "ConfigChangeTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_config_change_test);
if (savedInstanceState != null) {
// 恢复之前保存的数据
Log.d(TAG, "onCreate: savedInstanceState:" + savedInstanceState);
} else {
//
Log.d(TAG, "onCreate: savedInstanceState:" + savedInstanceState);
}
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
Log.d(TAG, "onSaveInstanceState:2 ");
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "onSaveInstanceState: ");
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG, "onRestoreInstanceState: ");
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d(TAG, "onConfigurationChanged: landscape");
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d(TAG, "onConfigurationChanged: portrait");
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
public void jumpToTest2(View view) {
Intent intent = new Intent(this, ConfigTest2Activity.class);
startActivity(intent);
}
public void addConfigFragment(View view) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_config, ConfigChangeFragment.class, null).commit();
}
}
布局文件activity_config_change_test.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_config"
android:layout_width="match_parent"
android:layout_height="200dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="29dp"
android:onClick="addConfigFragment"
android:text="添加Fragment" />
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="29dp"
android:onClick="jumpToTest2"
android:text="跳到第二个页面" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
LinearLayout>
用到的Fragment: ConfigChangeFragment.java
public class ConfigChangeFragment extends Fragment {
private static final String TAG = "ConfigChangeFragment";
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
public ConfigChangeFragment() {
Log.d(TAG, "ConfigChangeFragment: ");
}
public static ConfigChangeFragment newInstance(String param1, String param2) {
ConfigChangeFragment fragment = new ConfigChangeFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Log.d(TAG, "onAttach: ");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
Log.d(TAG, "onCreate: savedInstanceState:" + savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: ");
return inflater.inflate(R.layout.fragment_config_change, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, "onViewCreated: ");
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
Log.d(TAG, "onViewStateRestored: ");
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "onSaveInstanceState: ");
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated: ");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
public void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView: ");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach: ");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "onConfigurationChanged: ");
}
}
Fragment布局:fragment_config_change.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello Fragment" />
FrameLayout>
跳转到的第二个Activity页面就是一个空Activity,这里就不列了,就是为了测一下跳转到其他Activity时生命周期方法的调用而已。