protected void onRestoreInstanceState (Bundle savedInstanceState)
//Bundle: the data most recently 最近 supplied in onSaveInstanceState(Bundle). This value cannot be null.
This method is called after onStart()
when the activity is being re-initialized
from a previously saved state, given here in savedInstanceState
. Most implementations will simply use onCreate(Bundle)
to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state
that had previously been frozen by onSaveInstanceState(Bundle)
.
当 activity 从先前保存的状态重新初始化时,在 onStart()
之后调用此方法。大多数实现将简单地使用 onCreate(Bundle)
来恢复它们的状态,但有时在完成所有初始化后在这里执行它、或允许子类决定是否使用您的默认实现会很方便。此方法的默认实现会执行恢复先前被 onSaveInstanceState(Bundle)
保存的任何视图状态。
This method is called between onStart()
and onPostCreate(Bundle)
. This method is called only when recreating
an activity; the method isn't invoked if onStart()
is called for any other reason.
该方法在 onStart()
和 onPostCreate(Bundle)
之间调用。 此方法仅在重新创建
activity 时调用;如果出于任何其他原因调用 onStart()
,则不会调用该方法。
某些设备配置
可能会在运行时发生变化,例如屏幕方向、键盘可用性,以及当用户启用多窗口模式时。发生这种变化时,Android 会重启
正在运行的 Activity(先后调用onDestroy
和onCreate
)。重启行为旨在通过利用与新设备配置
相匹配的备用资源,来自动重新加载您的应用,从而帮助它适应新配置。
然而,重启应用并恢复大量数据不仅成本高昂,而且会造成糟糕的用户体验。在此情况下,您还有两个选择:
1、在配置变更期间保留对象,并使用 ViewModel 等方式保存界面数据
允许 Activity 在配置变更时重启,但是需将有状态对象传递给 Activity 的新实例。
2、阻止重启 Activity,并在回调中自行处理配置变更
如果您无法使用首选项(onSaveInstanceState()、ViewModel 和持久存储)来保留界面状态,则可阻止系统在特定配置变更期间重启您的 Activity。配置变更时,应用会收到回调,以便您可以根据需要手动更新 Activity。
允许重启并自行保存界面数据
如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。
此外,若使用系统通过 onSaveInstanceState()
回调为您保存的 Bundle
,则可能 无法完全恢复 Activity 状态,因为该类并非用于携带大型对象
(例如Bitmap
),并且其中的数据必须依次在主线程中进行序列化和反序列化
,而这可能会消耗大量内存并降低配置变更的速度。
在此情况下,您可通过使用 ViewModel
对象来减轻重新初始化 Activity 的负担。系统会在配置变更时保留 ViewModel,使其成为保存界面数据的理想场所,让您无需再次查询这些数据。
阻止重启并自行处理配置变更
如果应用在特定配置变更期间无需更新资源
,并且因性能限制您需要尽量避免 Activity 重启,则可声明 Activity 自行处理配置变更,从而阻止系统重启 Activity。
注意:自行处理配置变更可能会提高使用备用资源
的难度,因为系统不会为您自动应用
这些资源。只有在必须避免 Activity 因配置变更而重启的无奈情况下,您才可考虑使用此方法,并且不建议
对大多数应用使用此方法。
如要声明由 Activity 处理配置变更,请在清单文件中编辑相应的元素,以包含 android:configChanges
属性,该属性的值表示要处理的配置。您可以在属性中声明多个配置值,方法是用管道 |
字符将其进行分隔。
常用的值:
orientation
值可在屏幕方向发生变更时阻止重启
screenSize
值也可在屏幕方向发生变更时阻止重启,但仅适用于 API13 及以上版本的系统。若想在应用中手动处理配置变更,您必须在 android:configChanges
属性中声明 orientation
和 screenSize
值
keyboardHidden
值可在键盘可用性发生变更时阻止重启。
现在,即便其中某个配置发生变化, Activity
也不会重启,但 Activity
会接收到对 onConfigurationChanged()
的调用消息。此方法会收到传递的 Configuration
对象,从而指定新设备配置。您可以通过读取 Configuration
中的字段确定新配置,然后通过更新界面所用资源进行适当的更改。调用此方法时,Activity 的 Resources
对象会相应地进行更新,并根据新配置返回资源,以便您在系统不重启 Activity 的情况下轻松重置界面元素。
Configuration
对象代表所有当前配置,而不仅仅是已变更的配置。多数情况下,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为正在处理的配置提供备用资源。例如,由于 Resources
对象现已更新,您便可通过 setImageResource()
重置任何 ImageView
并使用合适的新配置资源。
请谨记:在声明由 Activity 处理配置变更时,您有责任 resetting any elements for which you provide alternatives(备用资源)。如果您声明由 Activity 处理方向变更,且需要在横向和纵向之间切换某些图像,则您必须在 onConfigurationChanged()
期间为每个元素 re-assign(重新分配) each resource。
如果无需根据这些配置变更
更新应用,则您可不必实现 onConfigurationChanged()
。在此情况下,all of the resources used before the configuration change are still used,区别在于您无需重启 Activity。但是,您的应用应始终能在保持先前状态完好的情况下
关闭和重启,因此您不应该认为,使用此方法即可无需保留正常 Activity 生命周期中的状态。其原因是一些其他配置变更
会强制重启应用,而且某些事件需由您进行处理,例如以下情况:when the user leaves your application and it gets destroyed before
the user returns to it.
横竖屏切换下的生命周期
不配置 screenSize 场景
下面三种方式 Log是一样的:横竖屏切换时会重新调用各个生命周期,且都是执行一次生命周期方法
不配置 android:configChanges
仅配置 android:configChanges="orientation"
仅配置 android:configChanges="orientation|keyboardHidden"
//竖屏切横屏
onPause
onSaveInstanceState
onStop
onDestroy
onCreate - orientation
onStart
onRestoreInstanceState
onResume
//横屏切竖屏
onPause
onSaveInstanceState
onStop
onDestroy
onCreate - orientation
onStart
onRestoreInstanceState
onResume
我们经常在其他地方看到,以上3种不同配置下,回调 Activity 生命周期方法次数并不一致,为什么和我们的结论不一样呢?
其实是 Android 高、低版本的差异而已,查看官方文档,发现有如下提示:
注意:从 Android 3.2(API13)开始,当设备在纵向和横向之间切换时, screenSize 也会发生变化。因此,在开发针对 API13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启,则除了orientation
值以外,您还必须添加screenSize
值。但是,如果您的应用是面向(注意,指的是targetSdkVersion) API12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(注意,即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。
目前不会还有兼容 API12 或更低版本的应用了,所以这三种配置都已经不适用了。
完整配置场景
android:configChanges="orientation|keyboardHidden|screenSize"
//竖屏切横屏
onConfigurationChanged
//横屏切竖屏
onConfigurationChanged
按照这种配置,横竖屏切换时才不会销毁 activity,且只调用 onConfigurationChanged
方法。
代码中动态切换横竖屏
在代码中切换屏幕的方向主要调用 setRequestedOrientation(int requestedOrientation)
方法,此方法的作用等同于在 AndroidManifest.xml
设置的 android:screenOrientation
。
boolean isLandscape = getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
Toast.makeText(TestActivity.this, "当前是 " + (isLandscape ? "横屏" : "竖屏"), Toast.LENGTH_SHORT).show();
setRequestedOrientation(isLandscape ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
注意: 通过 setRequestedOrientation
修改了屏幕方向后,就类似于设置了 android:screenOrientation
,效果是一样的。比如:调用 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
后,无论屏幕怎么旋转,都不会切换屏幕方向。如果要恢复为响应横竖屏随物理方向传感器设备变换,那么就需要手动调用类似 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR)
代码进行恢复。
适用场景:如果我们应用是手机和平板都可用的,但是手机上只能是竖屏不可切换,平板上只能是横屏不可切换,那么就可以在区分设备是手机还是平板后,在BaseActivity
的onCreate
方法中通过setRequestedOrientation
设置屏幕方向。
重启模式下数据的保存与恢复
在重启 Activity 模式下,横竖屏切换的时候会导致数据丢失,我们可以通过如下代码来保证数据不丢失:
public class TestActivity extends Activity {
private String date;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
if (savedInstanceState != null) {
date = savedInstanceState.getString("date"); //在 onCreate 中也是可以拿到之前保存的数据的
} else {
date = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date());
textView.setText(date);
setContentView(textView);
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("date", date); //putInt、putBoolean、putCharSequence、putIntArray、putIntegerArrayList...
super.onSaveInstanceState(outState);
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
String date = savedInstanceState.getString("date");
Toast.makeText(this, date, Toast.LENGTH_SHORT).show();
重启模式下的屏幕适配
重启模式下,如果大家在资源目录 res 中添加了 layout-land
(横向布局文件夹) 和 layout-port
(竖想布局文件夹),重启 Activity 模式的横竖屏切换,系统会自动帮我们显示正确方向的布局 UI。
非重启模式下的屏幕适配
非重启 Activity 模式下横竖屏切换时,我们的 Activity 不会销毁重建,数据也不会丢失。所以,如果按照上面的方式,在资源目录 res 中添加 layout-land
和 layout-port
,会发现并没有效果,此时应该怎么做呢?
其实也很简单,只需要在onConfigurationChanged
中再掉一次setContentView
即可。
public class TestActivity extends Activity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv);
mTextView.setText(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date()));
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.tv);
textView.setText("切换到了竖屏 " + (textView == mTextView));
//注意1:界面上的 TextView 已经不是原先 onCreate 中的 TextView 了,必须重新find一次
//注意2:由于 activity 没变,所以原先的数据都还存在,此时可以直接铺到新的 UI 上去
} else {
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.tv);
textView.setText("切换到了横屏 " + (textView == mTextView));
mTextView.setText(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date())); //没有效果
横竖屏切换对 Fragment 的影响
测试代码 Activity
public class TestActivity extends FragmentActivity {
private static final String TAG = "child";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {
Log.d("Activity", " -- onCreate has no child ");
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.root_view, BlankFragment.newInstance("baiqiantao"), TAG);
transaction.commit();
} else {
Log.d("Activity", " -- onCreate has child ");
测试代码 Fragment
public class BlankFragment extends Fragment {
private static final String ARGUMENT = "argument";
private String mArgument;
public static BlankFragment newInstance(String argument) {
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, argument);
BlankFragment fragment = new BlankFragment();
fragment.setArguments(bundle);
return fragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("Fragment", "-- onCreate");
Bundle bundle = getArguments();
if (bundle != null) {
mArgument = bundle.getString(ARGUMENT);
@Override
public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i("Fragment", "-- onCreateView");
TextView textView = new TextView(getActivity());
textView.setBackgroundColor(Color.RED);
textView.setText(mArgument);
return textView;
非重建 Activity 模式
横竖屏切换时,Fragment 和 Activity 都只会调用 onConfigurationChanged 方法,不会走其他生命周期的方法。
Fragment: -- onConfigurationChanged
Activity: -- onConfigurationChanged
重建 Activity 模式
带有 Fragment 即为 Fragment 的打印,其他则为 Activity 的打印
进入 Activity 的 Log
-- onCreate has no child
Fragment -- newInstance
Fragment -- onAttach
Fragment -- onCreate
Fragment -- onCreateView
Fragment -- onActivityCreated
Fragment -- onStart
-- onStart
-- onResume
Fragment -- onResume
横竖屏切换的Log
虽然 Fragment 不像 Activity 拥有 onRestoreInstanceState
方法,但是我们可以在 onActivityCreated
中获取之前保存的数据。
Fragment -- onPause
-- onPause
-- onSaveInstanceState save: name = bqt //【Activity 保存数据】
Fragment -- onSaveInstanceState save str //【Fragment 可以在此保存数据】
Fragment -- onStop
-- onStop
Fragment -- onDestroyView
Fragment -- onDestroy
Fragment -- onDetach
-- onDestroy
Fragment -- onAttach
Fragment -- onCreate //【Fragment 重建了。一定注意,是 new 了一个新的对象,而不是复用旧的对象】
-- onCreate get: name = bqt //【Activity 重建了且数据恢复了。同样,也是 new 了一个新的对象】
-- onCreate has child //【自动将新的 Fragment 添加进了新的 Activity】
Fragment -- onCreateView
Fragment -- onActivityCreated get //【Fragment 可以在此获取之前保存的数据】
Fragment -- onStart
-- onStart
-- onRestoreInstanceState get: name = bqt
-- onResume
Fragment -- onResume
2021-6-2
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/14843044.html