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();
                            }
                        }
                    }
                }
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章