關于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生命周期的區別,我目前感受:
- Fragment多了一對onCreateView/onDestroyView,大概也是因為Fragment相比Activity,更多的是做View展示。
- Fragment多了onAttach和onDetach,用于標識跟Activity連接。
- 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
這個類也是有onSaveInstanceState
和onRestoreInstanceState(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