關(guān)于Activity/Fragment的生命周期
首先說(shuō)一個(gè)題外話,周五測(cè)試提了一個(gè)bug,跳至下一個(gè)Fragment,再回到之前的fragment,之前對(duì)一個(gè)textview做的操作都沒(méi)了。
于是我又去看了下View的lifecircle,發(fā)現(xiàn)跟Activity還是很不一樣的,他從backstack中取出上一個(gè)fragment之后會(huì)走onCreateView,而inflateview和butterknifer bind view都是在onCreateView中做的(這是對(duì)的,onCreateView要返回view的,官方文檔也說(shuō)這個(gè)view里要返回這個(gè)fragment的root layout。onCreate的話,就是初始化必要組件用的),也就是說(shuō)view是被重建了的,所以view狀態(tài)都變成了初始狀態(tài)。
最終我是這么解決的這個(gè)業(yè)務(wù)問(wèn)題的:在onCreateView的update當(dāng)前fragment的view的方法里加了判斷,如果uidata中相應(yīng)的數(shù)據(jù)有,我就把textview更新。
Activity和Fragment生命周期的區(qū)別,我目前感受:
- Fragment多了一對(duì)onCreateView/onDestroyView,大概也是因?yàn)镕ragment相比Activity,更多的是做View展示。
- Fragment多了onAttach和onDetach,用于標(biāo)識(shí)跟Activity連接。
- start/stop(可見(jiàn)),resume/pause(在前臺(tái))這兩對(duì)是共有的,activity有個(gè)特殊的onRestart。
另外,注意到onAttach(Activity activity)在api 23以上已經(jīng)deprecated了,參數(shù)變了:
/*
* 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);
}
那就要注意,如果你覆寫這個(gè)新的方法,當(dāng)你的gradle中的target api小于23的時(shí)候,onAttach就不會(huì)被調(diào)用了(上周接入外部SDK的時(shí)候由于他們的so不支持targetApi23以上,所以我們的app改成了targetApi22。這就是風(fēng)險(xiǎn))。正確的做法是升級(jí)targetApi,或者同時(shí)寫兩個(gè)onAttach。
關(guān)于Fragment的狀態(tài)保存
我們組的App里用的是UIData的方案,我覺(jué)得很巧妙;在Fragment的onAttach()方法(另外,onCreate()方法里也把getActivity賦給mActivity)做了mActivity的恢復(fù)和mUiData的恢復(fù):
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.mActivity = (CPActivity) activity;
this.mUIData = mActivity.mUIData;
}
這樣可以避免getActivity由于onDetach造成的空指針。因?yàn)閛nDetach之后,getActivity就是null了;但是其實(shí)Fragment對(duì)象并沒(méi)有被GC回收,所以mActivity并沒(méi)有被回收。
另外,對(duì)于ViewPager+Fragment這樣的設(shè)計(jì),viewpager默認(rèn)情況當(dāng)你滑到第三頁(yè)的時(shí)候,第一頁(yè)的Fragment就會(huì)onDestroyView,這時(shí)候再回來(lái)第一頁(yè),很多東西都會(huì)重建了。這種情形要分情況,如果是實(shí)時(shí)性高的app,這么做無(wú)可厚非;如果是實(shí)時(shí)性低的app,那可以在onCreateView里判斷網(wǎng)絡(luò)數(shù)據(jù)是否為空,或者rootView是否為空,如果不為空就不請(qǐng)求網(wǎng)絡(luò),避免資源消耗。
Activity的狀態(tài)保存
我們的Activity會(huì)有一個(gè)public的mUidata,在onCreate(),onRestoreInstanceState()和onRestoreInstanceState()的時(shí)候,都會(huì)把它從savedInstanceState中存放/取出。
@Override
protected void onCreate(Bundle savedInstanceState) {
// 加載數(shù)據(jù)
if (savedInstanceState == null) {
//initUIData是抽象方法,子類實(shí)現(xiàn)
mUIData = initUIData();
} else {
//記得這里我還問(wèn)過(guò),為什么要重新set一次classLoader,領(lǐng)導(dǎo)解釋了,我大概憶起是classLoader在恢復(fù)的時(shí)候可能發(fā)生錯(cuò)亂
savedInstanceState.setClassLoader(getClass().getClassLoader());
mUIData = (UIData) savedInstanceState.getSerializable(UIDATA);
}
super.onCreate(savedInstanceState);
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
Activity的Fragment之間的數(shù)據(jù)通信
我看了下這個(gè)文章,提到了Handler,廣播/EventBus,接口,setArgument等方式;其中最后一種的setArgument方式是Fragment的設(shè)計(jì)師們的初衷,我之前也簡(jiǎn)單分析過(guò),它的優(yōu)勢(shì)是可以在Activity重建的時(shí)候恢復(fù)數(shù)據(jù),也正因?yàn)槿绱耍現(xiàn)ragment不推薦使用單例。相比之下我感覺(jué)我們組的UIData方案還是蠻好的。
View的狀態(tài)保存
不光Activity,View
這個(gè)類也是有onSaveInstanceState
和onRestoreInstanceState(android.os.Parcelable)
這對(duì)保存state的方法的。
對(duì)于一個(gè)復(fù)雜view,比如一個(gè)用戶填滿了的表單,如果用戶旋轉(zhuǎn)了下屏幕,數(shù)據(jù)就都沒(méi)了肯定不行。
View中有個(gè)類,View.BaseSavedState
,解釋是Base class for derived classes that want to save and restore their own onSaveInstanceState()。我們自定義地去保存View的state。
我們的JDRView中確實(shí)繼承了它,用于在保存和恢復(fù)的時(shí)候給uidata賦值,也必須要往parcel里添加和恢復(fù)uidata:
public static class SavedState extends BaseSavedState {
/**
* 保存的數(shù)據(jù)
*/
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是怎么恢復(fù)數(shù)據(jù)的?
JDRView的構(gòu)造函數(shù)
JDRView()->init(){
mUIData = initUIData();
initUI();}
在onRestoreSavedInstace的時(shí)候調(diào)用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的數(shù)據(jù)轉(zhuǎn)化成Json然后保存的SharedPreferences里去;在構(gòu)造函數(shù)初始化和onRestoreSavedInstance的時(shí)候都會(huì)給mUidata賦值,然后initUi/updateUi。分別用來(lái)從磁盤上恢復(fù)緩存數(shù)據(jù)和View被重建的時(shí)候恢復(fù)數(shù)據(jù)。
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數(shù)據(jù),onSuccess時(shí)updateUI。
((JDRView) cardView).loadData(null);
}
}
}
cardId除了在緩存的時(shí)候有用,還有什么用。從前似乎是傳頁(yè)面名稱的,現(xiàn)在改成cardId了。
ref:
http://www.lxweimin.com/p/662c46cd3b5f
http://blog.csdn.net/u012702547/article/details/47151001
http://www.bubuko.com/infodetail-828889.html