前言
针对activity和fragment状态的保存和恢复,首先来列举两个生活中的场景,并以此来说明现场保护的重要性。
比如你到银行开户,好不容易等到一个窗口,并且已经填完了开户申请单,就差短信验证码了,可短信验证码迟迟不来,后面的人等急了,因此你就把窗口让给他,让他先办,等他办完了,你的验证码也来了,于是你又回到窗口,这个时候,业务员递给你一张空白的申请单,让你重新填写,你一定会抱怨,他没有保存你之前的状态。
再比如,你到饭馆吃饭,刚吃了两口,这时候来了个电话,因为饭店太吵,于是你起身出去找个安静点的地方接听电话,几分钟后打完了电话,回来后发现饭菜不见了,你可能会质疑服务员,为什么不保留我的饭菜呢?
生活里的这些个场景都需要保存状态,就是当再次回来的时候状态还是离开时的样子,同样地,应用也需要保存状态,以便再次打开应用后还是原来的状态.
什么时候需要保存状态?
当Android系统配置发生改变时,比如屏幕方向,系统语言发生改变时,系统会销毁当前activity的对象,并重新创建一个与当前系统配置相匹配的activity对象(加载当前配置资源).
---下面以 横竖屏切换 为例---
Activity 现场保存方式
1.限定屏幕的方向
在配置清单文件中给activity便签添加一个screenOrientation属性将其设置为portrait
(竖屏)或者landscape(横屏),代码如下
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hejun.state"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
限定当前应用的屏幕方向为竖屏,当切换为横屏时,activity不会被销毁,依旧已竖屏方向显示
2.自己处理系统变更
当横竖屏使用同一套布局文件时,我们就可以自己处理系统变更,不需要Android系统重新创建activity,
需要在AndroidManifest.xml文件中给activity标签添加一个configChanges属性,将其设置为orientation|screenSize|keyboardHidden,这样做会告诉Android系统,屏幕方向发生变化时,我们自己来处理屏幕的方向、屏幕的宽高及键盘的可见性等这些配置的变化。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hejun.state"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:configChanges="keyboardHidden|screenSize|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
3.使用onSaveInstanceState方法
当需要保存状态的时候,onSaveInstanceState这个函数会被调用,然而什么时候需要保存状态呢?
按HOME键时:此时Activity处于停止状态(onStop),已经处在后台,当系统资源紧张的时候,就有可能把这个Activity销毁掉(onDestroy)。
被来电覆盖时:情况和上面差不多
一个Activity的状态主要由两部分组成:一个是成员变量的值;另外一个是构成界面的整个视图树上每个视图的状态。默认情况下,Android已经保存了视图的状态,系统定义的视图控件的状态,都会被保存下来但是有前提条件
(1):给需要保存的控件设置id属性
(2)实现了onSaveInstanceState回调(保存)
(3)实现onRestoreInstanceState回调(恢复)
也可以使用Bundle保存和恢复:(不同的布局资源)
要保存状态,我们就需要一个存放数据的容器,同样要恢复状态,也需要一个容器从里面读取数据,而Bundle就是这样一个容器。
Bundle(和HashMap有点像)可以存放一系列的键值对形式的数据。在需要保存状态的时候,我们给状态一个名字(键),然后把这个名字和状态的值存到Bundle对象里。
系统会管理Bundle对象,在系统重启Activity的时候,系统会把销毁的Activity的状态存到Bundle对象里,然后再把这个Bundle对象(新旧Activity使用的是同一个Bundle对象,可以自己尝试在上面的三个回调函数中打印log进行验证)传递给新创建的Activity对象。
Bundle提供了一系列put和get方法,put方法用于向Bundle中写数据,get方法用与从Bundle中读数据。
示例:保存系统的当前时间
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final String TIME = "time"; private String time ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.time); EditText editText = findViewById(R.id.text); /* 判断Bundle对象是否为空 是 获取系统当前时间 否 取出保存的数据 */ if (savedInstanceState != null){ time = savedInstanceState.getString(TIME); }else { time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(System.currentTimeMillis())); } textView.setText(time); Log.d(TAG, "onCreate: " + MainActivity.this); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //保存Textview中的时间 outState.putString(TIME,this.time); } }
Fragment的现场保护
保持Fragment对象,就是在手机处于水平和竖直方向时使用一个Fragment对象,告诉系统不要重启正在运行的Fragment对象。要做到这点,需做到:
1. 扩展Fragment
2. 在onCreate函数里调用setRetainInstance(true);
3. 把Fragment对象添加到Activity中;
4. 当Activity重启时,通过FragmentManager获取此Fragment对象
Fragment 代码:
public class BlankFragment extends android.support.v4.app.Fragment{ private int score = 0; private TextView textView; private Button button; public BlankFragment() { // Required empty public constructor } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); //设置setRetainInstance(true)
} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_blank, container, false); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); textView = getView().findViewById(R.id.score); button = getView().findViewById(R.id.button); textView.setText(String.valueOf(score)); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { textView.setText(String.valueOf(++score)); } }); } }
activity 代码:
public class Main2Activity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FragmentManager fragmentManager = this.getSupportFragmentManager(); //通过tag找到fragment Fragment fragment = fragmentManager.findFragmentByTag("blankFragment"); if (fragment == null){ fragment = new BlankFragment(); fragmentManager.beginTransaction().replace(android.R.id.content, fragment,"blankFragment").commit(); } } }
如果Fragment在配置发生变化的时候不需要加载不同的资源,最好使用上面的保持Fragment对象的方法;
但有的时候会需要对正在运行的Fragment进行重启,这样就涉及到如何把旧Fragment对象的状态保存起来,然后把保存的状态传递给新创建的Fragment对象,和Activity类似(无onRestoreInstanceState回调方法):
onSaveInstanceState:可以将Fragment对象的状态信息保存到Bundle对象当中;
onActivityCreated:会收到一个Bundle对象,可以将之前的状态读取出来,以便进行恢复。
1.删除(注释)ScoreFragment中onCreate方法中的保持Fragment对象的属性setRetainInstance(true); 2.在ScoreFragment中添加onSaveInstanceState方法,代码如下: @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); //保存状态 outState.putInt("score", this.score); Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]"); } 3.然后我们在onCreate方法中恢复状态(也可以在onActivityCreated方法中恢复),在onCreate方法中添加下面代码: if (savedInstanceState!=null) { //恢复状态 this.score=savedInstanceState.getInt("score"); } !