Activity/Fragment/View的狀態保存/數據恢復

關于Activity/Fragment的生命周期

首先說一個題外話,周五測試提了一個bug,跳至下一個Fragment,再回到之前的fragment,之前對一個textview做的操作都沒了。

于是我又去看了下View的lifecircle,發現跟Activity還是很不一樣的,他從backstack中取出上一個fragment之后會走onCreateView,而inflateview和butterknifer bind view都是在onCreateView中做的(這是對的,onCreateView要返回view的,官方文檔也說這個view里要返回這個fragment的root layout。onCreate的話,就是初始化必要組件用的),也就是說view是被重建了的,所以view狀態都變成了初始狀態。

最終我是這么解決的這個業務問題的:在onCreateView的update當前fragment的view的方法里加了判斷,如果uidata中相應的數據有,我就把textview更新。

Activity和Fragment生命周期的區別,我目前感受:

  1. Fragment多了一對onCreateView/onDestroyView,大概也是因為Fragment相比Activity,更多的是做View展示。
  2. Fragment多了onAttach和onDetach,用于標識跟Activity連接。
  3. start/stop(可見),resume/pause(在前臺)這兩對是共有的,activity有個特殊的onRestart。

另外,注意到onAttach(Activity activity)在api 23以上已經deprecated了,參數變了:

/* 
* onAttach(Context) is not called on pre API 23 versions of Android and onAttach(Activity) is deprecated 
* Use onAttachToContext instead 
*/  
   @TargetApi(23)  
   @Override  
   public void onAttach(Context context) {  
       super.onAttach(context);  
       onAttachToContext(context);  
   } 

那就要注意,如果你覆寫這個新的方法,當你的gradle中的target api小于23的時候,onAttach就不會被調用了(上周接入外部SDK的時候由于他們的so不支持targetApi23以上,所以我們的app改成了targetApi22。這就是風險)。正確的做法是升級targetApi,或者同時寫兩個onAttach。

關于Fragment的狀態保存

我們組的App里用的是UIData的方案,我覺得很巧妙;在Fragment的onAttach()方法(另外,onCreate()方法里也把getActivity賦給mActivity)做了mActivity的恢復和mUiData的恢復:

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mActivity = (CPActivity) activity;
        this.mUIData = mActivity.mUIData;
    }

這樣可以避免getActivity由于onDetach造成的空指針。因為onDetach之后,getActivity就是null了;但是其實Fragment對象并沒有被GC回收,所以mActivity并沒有被回收。

另外,對于ViewPager+Fragment這樣的設計,viewpager默認情況當你滑到第三頁的時候,第一頁的Fragment就會onDestroyView,這時候再回來第一頁,很多東西都會重建了。這種情形要分情況,如果是實時性高的app,這么做無可厚非;如果是實時性低的app,那可以在onCreateView里判斷網絡數據是否為空,或者rootView是否為空,如果不為空就不請求網絡,避免資源消耗。

Activity的狀態保存

我們的Activity會有一個public的mUidata,在onCreate(),onRestoreInstanceState()和onRestoreInstanceState()的時候,都會把它從savedInstanceState中存放/取出。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 加載數據
        if (savedInstanceState == null) {
//initUIData是抽象方法,子類實現
            mUIData = initUIData();
        } else {
//記得這里我還問過,為什么要重新set一次classLoader,領導解釋了,我大概憶起是classLoader在恢復的時候可能發生錯亂       
            savedInstanceState.setClassLoader(getClass().getClassLoader());
            mUIData = (UIData) savedInstanceState.getSerializable(UIDATA);
        }
        super.onCreate(savedInstanceState);

        imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    }

Activity的Fragment之間的數據通信

我看了下這個文章,提到了Handler,廣播/EventBus,接口,setArgument等方式;其中最后一種的setArgument方式是Fragment的設計師們的初衷,我之前也簡單分析過,它的優勢是可以在Activity重建的時候恢復數據,也正因為如此,Fragment不推薦使用單例。相比之下我感覺我們組的UIData方案還是蠻好的。

View的狀態保存

不光Activity,View這個類也是有onSaveInstanceStateonRestoreInstanceState(android.os.Parcelable)這對保存state的方法的。
對于一個復雜view,比如一個用戶填滿了的表單,如果用戶旋轉了下屏幕,數據就都沒了肯定不行。

View中有個類,View.BaseSavedState,解釋是Base class for derived classes that want to save and restore their own onSaveInstanceState()。我們自定義地去保存View的state。

我們的JDRView中確實繼承了它,用于在保存和恢復的時候給uidata賦值,也必須要往parcel里添加和恢復uidata:

    public static class SavedState extends BaseSavedState {
        /**
         * 保存的數據
         */
        private UIData mUIData;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            mUIData = (UIData) in.readSerializable();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeSerializable(mUIData);
        }

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {

            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

我回想了一下JDRView是怎么恢復數據的?
JDRView的構造函數
JDRView()->init(){
mUIData = initUIData();
initUI();}
在onRestoreSavedInstace的時候調用updateUI();

以FinanceView為例,

    @Override
    public UIData initUIData() {
        InvestmentListData investmentListData = mStorageUtil.get(InvestmentListData.class);
        if (investmentListData == null || investmentListData.fundCardInfo == null) {
            investmentListData = new InvestmentListData();
            investmentListData.fundCardInfo = createDefaultData();
        }
        return investmentListData;
    }

我們用的是mStorageUtil,作用是把Serializable的數據轉化成Json然后保存的SharedPreferences里去;在構造函數初始化和onRestoreSavedInstance的時候都會給mUidata賦值,然后initUi/updateUi。分別用來從磁盤上恢復緩存數據和View被重建的時候恢復數據。

MainSequenceFragment:

    protected void updateCardListUI(CardSequenceList cardSequenceList) {
        if (cardSequenceList == null) {
            return;
        }
        List<CardViewInfo> mCardList = cardSequenceList.tabStructureList;
        if (needDraw(mCardList)) {
            mCardView = CardViewManager.generateCardView(mActivity, mCardList);
            addCardViewLayout();
        }
        for (int i = 0; i < mCardView.size(); i++) {
            View cardView = mCardView.get(i);
            if (cardView instanceof JDRView) {
                //loadData利用cardId去取server數據,onSuccess時updateUI。
                ((JDRView) cardView).loadData(null);
            }
        }
    }

cardId除了在緩存的時候有用,還有什么用。從前似乎是傳頁面名稱的,現在改成cardId了。

ref:
http://www.lxweimin.com/p/662c46cd3b5f
http://blog.csdn.net/u012702547/article/details/47151001
http://www.bubuko.com/infodetail-828889.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容