Android 现场保护

前言

  针对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");
        }

 



原文地址:https://www.cnblogs.com/conglingkaishi/p/9914701.html