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

     



  • 相关阅读:
    SharePoint:扩展DVWP 第11部分:在工作流中使用更多的表单字段
    Guava学习笔记:Google Guava 类库简介
    每天一个linux命令(59):rcp命令
    每天一个linux命令(60):scp命令
    【转载】程序员要勇于说不
    深入理解Java:内省(Introspector)
    Guava学习笔记:Optional优雅的使用null
    深入理解Java:注解(Annotation)基本概念
    深入理解Java:SimpleDateFormat安全的时间格式化
    每天一个linux命令(58):telnet命令
  • 原文地址:https://www.cnblogs.com/conglingkaishi/p/9914701.html
Copyright © 2020-2023  润新知