文章目录

  • 1. Activity生命周期的变化
    • 1.1 正常生命周期
    • 1.2 屏幕旋转后重建Activity
    • 1.3 解决数据丢失问题--onSaveInstanceState和onRestoreInstanceState
    • 1.4 整个屏幕旋转过程调用的生命周期方法
    • 1.5 onCreate方法中的savedInstanceState参数
    • 1.6 onSaveInstanceState的调用时机
    • 1.7 注意事项
  • 2. 存在Fragment时的生命周期变化
  • 3. 如何防止页面重建
    • 3.1 android:configChanges
    • 3.2 onConfigurationChanged
  • 4. 源码

在android开发中,有一个容易被忽视但其实很重要的问题:屏幕旋转后的页面重建。
本文将介绍下 当屏幕旋转后,页面生命周期的变化以及 如何防止页面重建带来的问题

1. Activity生命周期的变化

1.1 正常生命周期

一个Activity从创建到退出,正常的生命周期流程是:

onCreate-->onStart-->onResume-->onPause-->onStop-->onDestroy

1.2 屏幕旋转后重建Activity

当屏幕旋转时,Android系统会自动将当前屏幕方向Activity销毁,再重新创建一个适应新屏幕方向的Activity
很自然的,我们能猜到会执行onPause-->onStop-->onDestroy-->onCreate-->onStart-->onResume这些方法。但除了这些Android还为我们考虑到了数据丢失的问题。

试想一下这种场景:页面中有一个TextView和一个Button,每点击一次Button,TextView显示点击的次数。当点击了一些次数后,旋转屏幕,你会发现TextView上记录的次数恢复到了最开始的状态。
原因也不难解释:因为整个Activity销毁了,重新创建的Activity是一个最开始的状态。

1.3 解决数据丢失问题–onSaveInstanceState和onRestoreInstanceState

为了处理这种由于销毁重建导致的页面数据丢失的问题,Android提供了另外两个方法 onSaveInstanceStateonRestoreInstanceState,算是对生命周期方法的补充吧。

@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是对状态的恢复。

1.4 整个屏幕旋转过程调用的生命周期方法

了解到这些,当屏幕旋转后回调的方法最终是这样的:

onPause-->onStop-->onSaveInstanceState-->onDestroy-->onCreate-->
onStart-->onRestoreInstanceState-->onResume

没错,onSaveInstanceState在onDestroy之前;onRestoreInstanceState在onResume之前。

1.5 onCreate方法中的savedInstanceState参数

除此之外,我们还应了解到,我们最常见的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类型的哦,也就是说他们互相传递毫无障碍,都是用来盛放键值对儿的。

1.6 onSaveInstanceState的调用时机

经过测试,以下情况都会调用onSaveInstanceState方法

  • 屏幕旋转
  • 锁屏
  • 按home键回到桌面
  • ActivityA 跳到 ActivityB,调ActivityA的onSaveInstanceState

而正常的退出Activity是不会调用onSaveInstanceState的

  • 按返回键退出Activity
  • 调用finish方法退出Activity

可见,onSaveInstanceState 就是在那些用户期望保留数据的场景才会调用,这也是它存在的初衷。

1.7 注意事项

  • onSaveInstanceState方法只适合保存少量的、不复杂的数据,如少量String,boolean等。
    因为它涉及到序列化反序列化等操作,如果数据复杂会比较耗时,导致页面卡顿。

2. 存在Fragment时的生命周期变化

除了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:

3. 如何防止页面重建

那么屏幕旋转后一定非要页面重建吗?当然不是,Android尽可能的为我们提供了选择。

3.1 android:configChanges

如果你不想让页面的Activity销毁重建的话,可以在AndroidManifest.xml文件的Activity节点里添加android:configChanges配置,如下:

<activityandroid:name=".config_change.ConfigChangeTestActivity"android:configChanges="orientation|screenSize" />

android:configChanges的值代表了哪些配置发生变化时页面不必重建。上述配置代码的orientation|screenSize意思是说,方向 | 屏幕大小 发生变化时页面不重建。

注意:经过本人测试,这里必须同时配置orientation|screenSize这两个值才能阻止页面重建,只配置一个orientation或者screenSize都是不行的。

另外,还有一些其他值:screenLayout|keyboardHidden等,有兴趣可以自行了解。

3.2 onConfigurationChanged

配置完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中,我们可以从中获取到新的屏幕方向等配置信息,进而做适配我们页面的处理。

4. 源码

最后贴出本文用到的案例源码,以供参考

主界面ConfigChangeTestActivity.java

public class ConfigChangeTestActivity extends AppCompatActivity {private static final String TAG = "ConfigChangeTestActivity";@Overrideprotected 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);}}@Overrideprotected void onStart() {super.onStart();Log.d(TAG, "onStart: ");}@Overrideprotected void onRestart() {super.onRestart();Log.d(TAG, "onRestart: ");}@Overrideprotected void onResume() {super.onResume();Log.d(TAG, "onResume: ");}@Overrideprotected void onPause() {super.onPause();Log.d(TAG, "onPause: ");}@Overrideprotected void onStop() {super.onStop();Log.d(TAG, "onStop: ");}@Overrideprotected void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy: ");}@Overridepublic void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {super.onSaveInstanceState(outState, outPersistentState);Log.d(TAG, "onSaveInstanceState:2 ");}@Overrideprotected void onSaveInstanceState(@NonNull Bundle outState) {super.onSaveInstanceState(outState);Log.d(TAG, "onSaveInstanceState: ");}@Overrideprotected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {super.onRestoreInstanceState(savedInstanceState);Log.d(TAG, "onRestoreInstanceState: ");}@Overridepublic 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

<?xml version="1.0" encoding="utf-8"?>
<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.FragmentContainerViewandroid:id="@+id/fragment_config"android:layout_width="match_parent"android:layout_height="200dp" /><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="29dp"android:onClick="addConfigFragment"android:text="添加Fragment" /><Buttonandroid:id="@+id/button4"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="29dp"android:onClick="jumpToTest2"android:text="跳到第二个页面" /><EditTextandroid: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;}@Overridepublic void onAttach(@NonNull Context context) {super.onAttach(context);Log.d(TAG, "onAttach: ");}@Overridepublic 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);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {Log.d(TAG, "onCreateView: ");return inflater.inflate(R.layout.fragment_config_change, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);Log.d(TAG, "onViewCreated: ");}@Overridepublic void onStart() {super.onStart();Log.d(TAG, "onStart: ");}@Overridepublic void onResume() {super.onResume();Log.d(TAG, "onResume: ");}@Overridepublic void onViewStateRestored(@Nullable Bundle savedInstanceState) {super.onViewStateRestored(savedInstanceState);Log.d(TAG, "onViewStateRestored: ");}@Overridepublic void onSaveInstanceState(@NonNull Bundle outState) {super.onSaveInstanceState(outState);Log.d(TAG, "onSaveInstanceState: ");}@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);Log.d(TAG, "onActivityCreated: ");}@Overridepublic void onPause() {super.onPause();Log.d(TAG, "onPause: ");}@Overridepublic void onStop() {super.onStop();Log.d(TAG, "onStop: ");}@Overridepublic void onDestroyView() {super.onDestroyView();Log.d(TAG, "onDestroyView: ");}@Overridepublic void onDetach() {super.onDetach();Log.d(TAG, "onDetach: ");}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy: ");}@Overridepublic void onConfigurationChanged(@NonNull Configuration newConfig) {super.onConfigurationChanged(newConfig);Log.d(TAG, "onConfigurationChanged: ");}
}

Fragment布局:fragment_config_change.xml

<?xml version="1.0" encoding="utf-8"?>
<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"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:text="Hello Fragment" /></FrameLayout>

跳转到的第二个Activity页面就是一个空Activity,这里就不列了,就是为了测一下跳转到其他Activity时生命周期方法的调用而已。

Android屏幕旋转后的变更--ConfigChange相关推荐

  1. Android 屏幕旋转 全解析

    屏幕旋转一般的解决方案 关于屏幕旋转这里,之前一直没太注意,因为根据设备会有指定的屏幕旋转策略如: 开发手机应用时一直使用强制竖屏布局 开发平板设备一直使用横屏布局 开发系统应用,一般给两套即横竖各一 ...

  2. Android屏幕旋转时Activity不重新调用onCreate的方法

    2019独角兽企业重金招聘Python工程师标准>>> android屏幕旋转时Activity不重新调用onCreate的方法 当手机转屏时,Activity的onDestroy和 ...

  3. android 旋转屏幕 不重走生命周期,屏幕旋转后Activity生命周期

    主要针对屏幕旋转对 Activity 生命周期有何影响. 第一种情况 在没有其它配置的情况下,通过日志打印屏幕旋转会调用的方法. //onPause()----onStop()-----onDestr ...

  4. Android 屏幕旋转时Activity的变化

    Android开发文档上专门有一小节解释这个问题.简单来说,Activity是负责与用户交互的最主要机制,任何"设置"(Configuration)的改变都可能对Activity的 ...

  5. android 屏幕旋转流程,android自动屏幕旋转流程分析.doc

    android自动屏幕旋转流程分析.doc android自动屏幕旋转流程分析 在android设置(Settings)中我们可以看到显示(display)下有一个自动屏幕旋转的checkbox, 如 ...

  6. Android 屏幕旋转的处理

    1. 不做任何处理的情况下 如果没有针对性地做任何处理的话,默认情况下,当用户手机的重力感应器打开后,旋转屏幕方向,会导致app的当前activity发生onDestroy-> onCreate ...

  7. android屏幕旋转生命周期,Activity、Fragment生命周期---横竖屏切换的生命周期

    先贴出一张大家众所周知activity流程图 onCreate():创建Activity调用,用于Activity的初始化,还有个Bundle类型的参数,可以访问以前存储的状态.onStart():A ...

  8. Android屏幕旋转180°的实现

    这次分享一个实现屏幕只能在竖直方向上旋转的功能,开发相机的童鞋应该都会遇到屏幕旋转的问题,一般都是横竖屏的切换,布局变换,生命周期问题啥的,这些网上一搜一大堆的解决方案,什么监听onConfigura ...

  9. 通过广播获取Android屏幕旋转事件

         Android获取系统屏幕旋转的方式有几种,其中比较常见的是通过重写Activity中的onConfigurationChanged方法,但是这种方法有个缺陷,当测试程序在后台运行的时候不能 ...

最新文章

  1. 【Appium】Appium工作原理
  2. 【正一专栏】中国足球不是你想不玩就不玩的
  3. 1030 Travel Plan (30 分) 【难度: 中 / 知识点: 最短路】
  4. 骑手困在系统里,网友困在回应里,而王兴正在刷饭否
  5. Judges' Time Calculation
  6. Redis学习之复制(三)
  7. 进程间通讯-3(Manager)-实现数据的同时修改
  8. mysql-connector-odbc-5.3.12-win32.msi安装步骤
  9. 自己创建一个本地服务器,实现文件下载
  10. 编程基础(四)——cache之一
  11. mysqlL时间戳和时间的获取/相互转换/格式化
  12. vtkdelaunay3d的参数设置_VTK 渲染体数据并加方位标注
  13. 中台架构的未来在哪—开放式架构
  14. mysql的应用领域_面向应用领域的数据库新技术汇总(干货)
  15. EtherCAT运动控制卡开发教程之Qt(下):SCARA机械手正反解的建立
  16. erlang send_after 源码剖析
  17. 【林轩田】机器学习基石(九)——线性回归
  18. web 前端常见英文汇总
  19. selenium 与浏览器 以及浏览器驱动版本问题
  20. 内连接、外连接、全外连接、交叉连接用法汇总(个人记录使用)

热门文章

  1. ubuntu18.04中基于Docker搭建tensorflow-gpu开发环境
  2. 豆瓣读书top250数据爬取与可视化
  3. cups共享linux打印机_利用CUPS为linux安装打印服务并局域网共享
  4. 如何给播放器增加倍速播放
  5. 全球十大公司物联网战略,一个万物智能的世界即将到来
  6. PPT和PPTX的区别是什么
  7. 个人简历技能特长或爱好计算机,四种适合写进个人简历的特长或爱好
  8. cad放大_左手快捷键,右手鼠标,这就是CAD!
  9. 超详细的网络抓包神器 tcpdump 使用指南
  10. 阿里云esc服务器上装hadoop