Activity/Fragment Field字段值自动保存和恢复

app 被系统意外杀死(包括横竖屏切换) Activity/Fragment中的字段通过自定义注解+反射实现自动恢复

源码

github
csdn

当App意外被杀死,如长时间滞留后台,横竖屏切换,这时再进入app,并不是正常启动app(不会走入口流程)。
这时候Activity/Fragment 中字段就需要临时保存和恢复。

  override fun onSaveInstanceState(outState: Bundle) {
          super.onSaveInstanceState(outState)
           //通过Bundle 保存临时数据
    }
  override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           if(savedInstanceState != null) {
            //Bundle 重新恢复数据
           }
   }

上面代码比较简单,唯一的问题就是需要自己反复的保存和读取,重复代码比较多。
下面通过自定义注解和反射的方式实现字段自动恢复(可以通过开发者选项,打开不保留后台选项,模拟后台杀死app,或者横竖屏切换)

创建一个基类 BaseActivity 或者 BaseFragment

    abstract class BaseActivity : Fragment(), IDisplay { 
             //通过 ObjectInstanceManager实现保存和恢复字段,包括恢复字段类型为fragment
             private var mOIM: ObjectInstanceManager = ObjectInstanceManager()
    
             override fun onCreate(savedInstanceState: Bundle?) {
                 super.onCreate(savedInstanceState)
                 //....
                 if(savedInstanceState != null) {
                    //重新恢复fragment
                    mOIM.againFragmentInstance(supportFragmentManager, this)
                    //重新恢复字段
                    mOIM.againFieldInstance(savedInstanceState, this)
                 }
             }
         
             override fun onSaveInstanceState(outState: Bundle) {
                 super.onSaveInstanceState(outState)
                 //保存字段
                 mOIM.saveField(outState, this)
             }
       }

使用

        class MainActivity : BaseAct() {
        
            //添加AgainFragmentInstance注解,可以重新实例化fragment
            @AgainFragmentInstance
            private var mainFragment: MainFragment? = null
        
            //未指定key, 字段名作为key
            @AgainInstance
            private val age = 10
            /**
             * 可以手动指定key
             */
            @AgainInstance(key = "price")
            private val mPrice = 2000
        
            //第一次正常初始化字段
            override fun onInitData() {
                //fragment 只要初始化一次
                mainFragment = MainFragment.newInstance("this is main_fragment")
                supportFragmentManager.beginTransaction().replace(R.id.contentPanel, mainFragment!!)
                    .commitAllowingStateLoss()
            }
        
            override fun onResume() {
                super.onResume()
                Timber.d("${mainFragment == null}")
                Timber.d("age $age")
                Timber.d("user $mUser")
                Timber.d("price $mPrice")
                Timber.d("userList $mUserLis")
                Timber.d("userMap $mUserMap")
                Timber.d("map $mMap")
            }
        
        }

实现逻辑

主要通过自定义注解来标识需要重新实例化的字段,再通过反射把字段存储到Bundle,和从Bundle重新获取重新赋值。

  1. @AgainInstance 标识字段
    @Keep
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AgainInstance {
        /**
         * 通过onSaveInstanceState保存
         * 不指定key的情况使用字段名作为key
         * @return key作为 Bundle的key
         */
        String key() default "";
    }
  1. 找到有@AgainInstance标识的字段,判断类型根据不同类型,存放Bundle中
    public class ObjectInstanceManager {
    
        /**
         * 保存 @AgainInstance注解绑定的field
         *
         * @param outState           通过Bundle
         * @param activityOrFragment 只能是activity或者fragment
         */
        public void saveField(Bundle outState, Object activityOrFragment) {
            //获取对象中的所有字段
            Field[] declaredFields = activityOrFragment.getClass().getDeclaredFields();
            if (declaredFields.length == 0) return;
            for (Field field : declaredFields) {
                //标记 AgainInstance注解的才处理
                if (field.isAnnotationPresent(AgainInstance.class)) {
                    field.setAccessible(true);
                    AgainInstance annotation = field.getAnnotation(AgainInstance.class);
                    String key = annotation.key();
                    //如果没有自定义key,那么直接使用属性名
                    if(TextUtils.isEmpty(key)) {
                        key = field.getName();
                    }
                    try {
                        //如果字段 值为null,不处理
                        if (field.get(activityOrFragment) == null) continue;
                        //以下根据字段的不同类型,保存到Bundle中
                        if (field.getType() == short.class) {
                            outState.putShort(key, field.getShort(activityOrFragment));
                        } else if (field.getType() == int.class) {
                            outState.putInt(key, field.getInt(activityOrFragment));
                        } else if (field.getType() == long.class) {
                            outState.putLong(key, field.getLong(activityOrFragment));
                        } else if (field.getType() == float.class) {
                            outState.putFloat(key, field.getFloat(activityOrFragment));
                        } else if (field.getType() == double.class) {
                            outState.putDouble(key, field.getDouble(activityOrFragment));
                        } else if (field.getType() == String.class) {
                            outState.putString(key, (String) field.get(activityOrFragment));
                        } else if (!isParcelableArray(field.getType()) && isSerializable(field.getType())) {
                            outState.putSerializable(key, (Serializable) field.get(activityOrFragment));
                        } else if (isParcelable(field.getType())) {
                            outState.putParcelable(key, (Parcelable) field.get(activityOrFragment));
                        } else if (field.getType() == short[].class || field.getType() == Short[].class) {
                            outState.putShortArray(key, (short[]) field.get(activityOrFragment));
                        } else if (field.getType() == int[].class || field.getType() == Integer[].class) {
                            outState.putIntArray(key, (int[]) field.get(activityOrFragment));
                        } else if (field.getType() == long[].class || field.getType() == Long[].class) {
                            outState.putLongArray(key, (long[]) field.get(activityOrFragment));
                        } else if (field.getType() == float[].class || field.getType() == Float[].class) {
                            outState.putFloatArray(key, (float[]) field.get(activityOrFragment));
                        } else if (field.getType() == double[].class || field.getType() == Double[].class) {
                            outState.putDoubleArray(key, (double[]) field.get(activityOrFragment));
                        } else if (isParcelableArray(field.getType())) { //不支持
    //                        Parcelable[] parcelables = (Parcelable[]) field.get(activityOrFragment);
    //                        ArrayList<Parcelable> parcelables1 = (ArrayList<Parcelable>) Arrays.asList(parcelables);
    //                        outState.putParcelableArrayList(key, parcelables1);
                            throw new RuntimeException(activityOrFragment.getClass().getName() + "--> 属性字段:" + field.getName() + " 不支持Parcelable[]类型 ");
                        } else if (field.getType() == List.class || field.getType() == ArrayList.class) {
                            saveListType(outState, activityOrFragment, field, key);
                        } else {
                            throw new RuntimeException(activityOrFragment.getClass().getName() + ": 该类型不支持" +
                                    field.getType().getName() +
                                    ", 支持的类型 包括基本类型,基本类型包装类,实现了Serializable,Parcelable接口对象,数组,一级List集合");
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
   
    }

  1. 重新还原字段值
    public class ObjectInstanceManager {
    
        /**
         * 重新实例化 带有@AgainInstance注解 的field
         *
         * @param savedInstanceState
         * @param activityOrFragment
         */
        public void againFieldInstance(@NonNull Bundle savedInstanceState, @NonNull Object activityOrFragment) {
            Field[] declaredFields = activityOrFragment.getClass().getDeclaredFields();
            if (declaredFields.length == 0) return;
            for (Field field : declaredFields) {
                try {
                    if (field.isAnnotationPresent(AgainInstance.class)) {
                        field.setAccessible(true);
                        AgainInstance annotation = field.getAnnotation(AgainInstance.class);
                        String key = annotation.key();
                        //如果没有指定key,就直接使用属性名称
                        if(TextUtils.isEmpty(key)) {
                            key = field.getName();
                        }
                        Object val = null;
                        if (field.getType() == short.class) {
                            val = savedInstanceState.getShort(key);
                        } else if (field.getType() == int.class) {
                            val = savedInstanceState.getInt(key);
                        } else if (field.getType() == long.class) {
                            val = savedInstanceState.getLong(key);
                        } else if (field.getType() == float.class) {
                            val = savedInstanceState.getFloat(key);
                        } else if (field.getType() == double.class) {
                            val = savedInstanceState.getDouble(key);
                        } else if (field.getType() == String.class) {
                            val = savedInstanceState.getString(key);
                        } else if (!isParcelableArray(field.getType()) && isSerializable(field.getType())) {
                            val = savedInstanceState.getSerializable(key);
                        } else if (isParcelable(field.getType())) {
                            val = savedInstanceState.getParcelable(key);
                        } else if (field.getType() == short[].class || field.getType() == Short[].class) {
                            val = savedInstanceState.getShortArray(key);
                        } else if (field.getType() == int[].class || field.getType() == Integer[].class) {
                            val = savedInstanceState.getIntArray(key);
                        } else if (field.getType() == long[].class || field.getType() == Long[].class) {
                            val = savedInstanceState.getLongArray(key);
                        } else if (field.getType() == float[].class || field.getType() == Float[].class) {
                            val = savedInstanceState.getFloatArray(key);
                        } else if (field.getType() == double[].class || field.getType() == Double[].class) {
                            val = savedInstanceState.getDoubleArray(key);
                        } else if (isParcelableArray(field.getType())) { //不支持Parcelable[]
    //                        ArrayList<Parcelable> parcelableArrayList = savedInstanceState.getParcelableArrayList(key);
    //                        Parcelable[] parcelables = parcelableArrayList.toArray(new Parcelable[]{});
    //                        val = parcelables;
                        } else if (field.getType() == List.class || field.getType() == ArrayList.class) {
                            val = againListType(savedInstanceState, field, key);
                        } else {
                            throw new RuntimeException(activityOrFragment.getClass().getName() + ": 该类型不支持" +
                                    field.getType().getName() +
                                    ", 支持的类型 包括基本类型,基本类型包装类,实现了Serializable,Parcelable接口对象,数组,一级List集合");
                        }
                        field.set(activityOrFragment, val);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

  1. 字段类型为 Fragment 类型 重新还原
    public class ObjectInstanceManager {
    
    
        /**
         * 当app意外被杀死,重启后,重新实例化fragment
         *
         * @param fragmentManager activity 的 SupportFragmentManager, fragment 的 childFragmentManager
         * @param activityOrFragment 必须是fragment或者activity
         */
        public void againFragmentInstance(FragmentManager fragmentManager, Object activityOrFragment) {
            if (fragmentManager.getFragments().size() <= 0) return;
            //找出fragment 列表
            List<Fragment> fragments = fragmentManager.getFragments();
            //获取字段列表
            Field[] declaredFields = activityOrFragment.getClass().getDeclaredFields();
            if (declaredFields.length == 0) return;
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(AgainFragmentInstance.class)) {
                    AgainFragmentInstance annotation = field.getAnnotation(AgainFragmentInstance.class);
                    for (Fragment fragment : fragments) {
                        //注解中不带key,并且key不等于当前的tag 跳过
                        if (!TextUtils.isEmpty(annotation.tag())
                                && !annotation.tag().equals(fragment.getTag())) {
                            continue;
                        }
                        //两种情况, 注解没有设置tag,那么直接判断字段类型的全类名和fragment列表中的fragment的全类名比较是否同一个。
                        //注解设置了tag,并且fragment的tag === 自定义注解的tag,那么再做上面的全类名比较
                        if (fragment.getClass().getName().equals(field.getType().getName())) {
                            field.setAccessible(true);
                            try {
                                field.set(activityOrFragment, fragment);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章