0 認知
Fragment官方的翻譯名為:片段,表示 Activity中的行為或用戶界面部分。
相比Activity
相比Activity,Fragment的創建、銷毀只需要依附到宿主Activity中,不需要與ActivityManagerService跨進程交互,所有的生命周期在宿主Activity中完成,可以在多個FragmentActivity中被多次重用,所以它更加靈活。
相比View
相比View,它擁有更多的生命周期(onAttach、onCreate、onCreateView、onStart、onResume、onPause、onStop、onDestroyView、onDestroy),可以管理menu,持有Activity引用(View持有的context有可能為ContextThemeWrapper對象),更利于模塊化。
1 構造
Fragment有兩種方式創建并依附到宿主Activity。
fromLayout方式
在xml中配置fragment標簽,例如
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:tag="tag"
android:name="com.asha.fragmentdemo.MyDialogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
記得fragment必須帶上1、android:name;2、android:tag或android:id二選一,否則會在創建過程中檢查參數時報錯。
當這個layout被inflate后,LayoutInfalter會回調Activity中的FragmentManager去處理這個tag,根據android:name實例化此tag對應的Fragment對象,通知它生成并返回view。隨后由FragmentManager管理此Fragment的生命周期。
FragmentManager方式
在代碼中實例化Fragment,被創建一個Bundle作為參數存儲載體賦值給Fragment,隨后通過FragmentManager開啟個transaction、add Fragment、commit,隨后某個時間點,FragmentMangager會處理此commit提交的aciton,完成Fragment的依附,示例代碼如下:
blankFragment = new BlankFragment();
Bundle bundle = new Bundle();
bundle.putInt("data",-1);
blankFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.container, blankFragment, "BlankFragmentTag").commit();
通過setArguments(bundle)有利于保存與恢復,后面會有介紹。
2 狀態
fragment的狀態變化由FragmentManager管理,fragment狀態主要可以分為以下6種:
//android.support.v4.app.Fragment
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3; // Fully created, not started.
static final int STARTED = 4; // Created and started, not resumed.
static final int RESUMED = 5; // Created started and resumed.
2.1 fragment狀態變化
fragment狀態變化主要來自以下兩個方面:
宿主FragmentActivty的生命周期變化
FragmentActivity生命周期的變化會調用FragmentController的對應回調,如當FragmentActivity調用onDestory后,成員變量FragmentController對象被調用了dispatchDestroy,代碼如下
//android.support.v4.app.FragmentActivty
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
@Override
protected void onDestroy() {
super.onDestroy();
doReallyStop(false);
mFragments.dispatchDestroy();
mFragments.doLoaderDestroy();
}
對應FragmentController內傳遞了給了構造時聚合進來的mHost對象中的FragmentManager對象,代碼如下
// android.support.v4.app.FragmentController
public void dispatchDestroy() {
mHost.mFragmentManager.dispatchDestroy();
}
隨后調用到FragmentManager中的moveToState方法,處理狀態改變,代碼如下
// android.support.v4.app.FragmentManager
public void dispatchDestroyView() {
moveToState(Fragment.CREATED, false);
}
FragmentTransaction
與上一條直接在主線程中立即調用不同,FragmentTransaction添加一系列add、remove、replace操作op并鏈表形式存儲,執行commit后,會在FragmentManager.enqueueAction,通過handler.post方法在主線程中下一個未知時間點執行此action,此action代碼如下:
// android.support.v4.app.BackStackRecord extends FragmentTransaction
public void run() {
...
Op op = mHead;
while (op != null) {
...
switch (op.cmd) {
case OP_ADD:
...
mManager.addFragment(f, false);
break;
case OP_REPLACE:
...
mManager.removeFragment(old, transition, transitionStyle);
...
mManager.addFragment(f, false);
...
break;
case OP_REMOVE:
...
mManager.removeFragment(f, transition, transitionStyle);
break;
case OP_HIDE:
...
break;
case OP_SHOW:
...
break;
case OP_DETACH:
...
break;
case OP_ATTACH:
...
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
op = op.next;
}
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
...
}
在上述removeFragment、addFragment等操作都會進入對應的moveToState函數,最后mManager調用moveToState函數來同步目前管理的fragment狀態遷移到mManager.mCurState狀態。
2.2 moveToState
FragmentManager中moveToState函數有多個參數形式,moveToState方法所有形參定義如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f)
void moveToState(int newState, boolean always)
void moveToState(int newState, int transit, int transitStyle, boolean always)
void moveToState(Fragment f, int newState, int transit, int transitionStyle,boolean keepActive)
舉個例子,當FragmentActivity已經onResume后,即FragmentActivity已經顯示在屏幕中,此時FragmentActivity中onResume已經調用了mFragmentManager的dispatchResume函數,即
// android.support.v4.app.FragmentManager
public void dispatchResume() {
mStateSaved = false;
moveToState(Fragment.RESUMED, false);
}
通過層層調用,進入到了上述第三個moveToState,在此代碼中,FragmentManager實例的成員變量mCurState直接被賦值為Fragment.RESUMED狀態,隨后遍歷實例內管理的mActive數組中的fragment對象,讓他們進入到Fragment.RESUMED狀態,代碼如下:
// android.support.v4.app.FragmentManager
int mCurState = Fragment.INITIALIZING;
void moveToState(int newState, int transit, int transitStyle, boolean always) {
...
mCurState = newState;
if (mActive != null) {
...
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle, false);
...
}
}
...
}
}
先忽略現有fragment的狀態遷移,如果此時有新的fragment通過FragmentTransaction加入到mManager(為FragmentManager實例),上面分析過android.support.v4.app.BackStackRecord會在某個時間點執行action,此時新的frament被加入到mManager.mActive數組中,同時會調用mManager.moveToState同步到現有狀態,即Fragment.RESUME/5,代碼如下:
// android.support.v4.app.BackStackRecord extends FragmentTransaction
public void run() {
...
// mManager為FragmentManager實例
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
...
}
所以執行到了FragmentManager的第四個moveToState,此時新的Fragment剛被創建成功,成員變量mState默認INITIALIZING/0,
// android.support.v4.app.Fragment
int mState = INITIALIZING;
FragmentManager的第四個moveToState函數帶有Fragment參數,進入后先會使用f.mState(即fragment當前狀態)與目標狀態newState進行比較,f.mState即0 < newState即5,則fragment需要從狀態0到狀態5,分別需要經歷INITIALIZING/0、CREATED/1、ACTIVITY_CREATED/2、STOPPED/3、STARTED/4,最后賦值為5,注意switch中沒有break,需要一直按順執行,不同的狀態分支需要執行不同的函數通知fragment進入此狀態,同時回調fragment中對應的生命周期函數。從代碼框架如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
if (f.mState < newState) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
...
f.onAttach(mHost.getContext());
...
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
}
f.mRetaining = false;
if (f.mFromLayout) {
...
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
if (!f.mFromLayout) {
...
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
f.performActivityCreated(f.mSavedFragmentState);
...
}
case Fragment.ACTIVITY_CREATED:
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
...
f.performResume();
...
}
}
} else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
f.performPause();
...
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
f.performStop();
}
case Fragment.STOPPED:
if (newState < Fragment.STOPPED) {
f.performReallyStop();
}
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
...
f.performDestroyView();
...
}
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
...
if (!f.mRetaining) {
f.performDestroy();
}
f.onDetach();
}
}
}
f.mState = newState;
}
同理從RESUMED狀態被destroy,需要從5遷移到0,執行上述函數f.mState > newState部分的邏輯,一步一步回到INITIALIZING狀態。
當然如果宿主Activity與fragement同時被銷毀,fragement會接收到FragmentActivity對應的生命周期dispatch,從5到4,從4到3,從3到2,從2到1,從1到0即可完成狀態遷移。
3 生命周期
先來一張官方文檔的圖,第二章已經介紹了狀態遷移,對應的狀態改變會調用對應的生命周期回調,調用時機已經非常清晰,說一下幾個注意點:
onAttach后即持有activity引用
不要被onActivityCreated迷惑,fragment.onAttach時就可以使用activity了,因為在f.onAttach前就進行了一系列基礎變量的賦值,代碼如下:
// android.support.v4.app.FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
if (f.mState < newState) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
...
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
f.mCalled = false;
f.onAttach(mHost.getContext());
...
}
...
}
...
}
onActivityCreated是一個什么狀態?
FragmentActivity在調用onCreate開始的時候調用FragmentManager實例的dispatchCreate函數,在FragmentActivity在調用onCreate結束的時候會調用dispatchActivityCreated,即通知fragment的activity已經調用onCreate完畢。
為什么fromLayout的Fragment在INITIALIZING階段就需要onViewCreated?
FragmentActivity在onCreate的時候調用了setContentView,此時需要通過R.layout.xxx方式或者直接LayoutInflater的方式創建view,LayoutInflater創建view時在createViewFromTag函數內會根據xml里定義的tag和attr去實例化對應的View,當LayoutInflater讀取到fragment這個tag后,先讓LayoutInflater內的context去處理onCreateView,看是否能返回對應的view,層層調用進入FragmentManager.onCreateView去實例化fragment、賦值、狀態遷移,關鍵是在這個時候就需要返回fragment內對應的view,此時FragmentActivity在onCreate階段,FragmentManager應該在Fragment.CREATED階段,所以狀態同步時newStateFragment.CREATED,在Fragment.CREATED分支無法調用onViewCreated,而fromLayout的Fragment在INITIALIZING階段就需要創建view并返回了,所以在INITIALIZING就得調用了onViewCreated了。
如果fragment.setRetainInstance(true),在一定情況下生命周期函數調用就發生改變了
這個一定情況是指configChange的情況,下一章具體講。
4 狀態保存
4.1 需要保存哪些東西?
Fragment
如果需要重新構造一個除了內存地址不一樣,屬性與原來實例一模一樣的Fragment,需要序列化以下四個對象或屬性:
- FragmentState
FragmentState包含了重新構造這個Fragment所需的最基本的屬性,包括完整類名、在mActive內的index,是否從layout生成的,id,tag,容器id,是否retainInstance,是否已經detached,構造時傳入的參數,定義如下:
// android.support.v4.app.FragmentState
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
}
- ViewState
Fragment內部管理的view的狀態,我們知道view自身有一套狀態保存的機制,通過根節點的view一層一層dispatch出去(dispatchSaveInstanceState、dispatchRestoreInstanceState)觸發保存和恢復先前的狀態。這種恢復屬于view被新建實例后恢復原來的狀態,比如EditText選中了一段文字,旋轉屏幕重新創建view實例,會重新focus,重新選中剛剛所選的那段文字。
而由Fragment管理的view脫離了原來的dispatch流程,是由Fragment自主管理觸發saveViewState和restoreViewState,脫離dispatch的方法在sdk11之前wrap一個NoSaveStateFrameLayout,11及之后直接設置屬性即可,代碼如下:
// android.support.v4.app.FragmentManager
// moveToState
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
if (Build.VERSION.SDK_INT >= 11) {
ViewCompat.setSaveFromParentEnabled(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wrap(f.mView);
}
...
}
- mUserVisibleHint
用這個標記可以控制Fragment是否延遲執行mLoaderManager內的任務,即如果mUserVisibleHint為false,這個Fragment在需要遷移到大于STOPPED的狀態時先忽略,當所有其他mUserVisibleHint為true的Fragment內的runningLoader執行完成,再遷移到FragmentManager現有狀態。
那為什么是停止在STOPPED狀態?
因為這個狀態下一個狀態就會觸發Fragment.onStart,onStart會調用這個Fragment內的mLoaderManager啟動內部的loader去做加載操作,如果延遲加載這部分,可以讓其他更重要的loader做完操作后再進行,提升體驗。
- 可自定義的onSaveInstanceState
這個與Activity一致,不再贅述。
FragmentManager
FragmentManager與Fragment一樣,都有一個序列化、反序列化基礎屬性的State類:FragmentManagerState。FragmentManagerState保存三個可序列化對象數組:
// android.support.v4.app.FragmentManagerState
final class FragmentManagerState implements Parcelable {
FragmentState[] mActive;
int[] mAdded;
BackStackState[] mBackStack;
}
對應保存FragmentManager內的三個數組屬性,定義如下:
// android.support.v4.app.FragmentManager
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
mAdded內保存的是所有add到FragmentManager內的Fragemnt,mActive中包含了所有mAdded對象外,還保存了與backStack相關的所有Fragment。所以說mAdded是mActive的子集,對應序列化對象時,mAdded只需要記住這個Fragment對象在mActive中的索引值,就可以找回原來Fragment對應的新Fragment。mBackStack保存了所有addToBackStack的FragementTransaction,可以記錄某次commit操作所有Fragment的變化,便于按下back鍵后回滾到上一步。
4.2 Fragment保存機制
fragment有兩種保存機制,一種是fragment.onSaveInstanceState方式,另一種是fragment.setRetainInstance(true)方式,我們來看看以下幾個經常出現的場景:
宿主FragmentActivity從后臺直接恢復
由于FragmentActivity只是在onStop狀態,FragmentActivity內的FragmentManager實例狀態為STOPPED狀態,FragmentManager實例和其內部管理的Fragment實例都還健在,只是需要從STOPPED狀態遷移到RESUMED即可。
FragmentActivity的recreate
recreate有兩種情況會觸發,一種是直接調用Activity.recreate(),另一種是RELAUNCH_ACTIVITY。兩種方式走到AMS層后都是走相同的流程。
RELAUNCH_ACTIVITY會在旋轉屏幕等onConfigurationChanged的情況未被Activity處理后發生。例如發生了ConfigurationChanged,而Manifest.xml中此Activity的android:configChanges沒有配置此Configuration,即Activity不處理此Configuration,AMS就會RELAUNCH此Activity。
發生recreate后,AMS會銷毀現有的Activity實例,重新啟動一個新的Activity實例。
如果Fragment設置了fragment.setRetainInstance(true)
AMS在銷毀舊Activity實例時會調用ActivityThread.performDestoryActivity -> Activity.retainNonConfigurationInstances -> FragmentActivity.onRetainNonConfigurationInstances -> FragmentController.retainNonConfig -> mFragmentManager.retainNonConfig,在FragmentManager中返回了mActive數組拷貝,代碼如下:
// android.support.v4.app.FragmentManager
ArrayList<Fragment> retainNonConfig() {
ArrayList<Fragment> fragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mRetainInstance) {
if (fragments == null) {
fragments = new ArrayList<Fragment>();
}
fragments.add(f);
f.mRetaining = true;
...
}
}
}
return fragments;
}
我們來看看Activity中保存一部分實例,代碼如下:
// android.app.Activity
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
List<Fragment> fragments = mFragments.retainNonConfig();
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
nci所保存的對象可以是任意對象,不需要做序列化和反序列化操作,恢復時是原有對象實例。nci對象返回給ActivityThread,保存在此進程的ActivityThread對象的mActivities鍵值對的此Activity Binder對應的ActivityClientRecord中。值得注意的是,nci.activity是在Activity中調用onRetainNonConfigurationInstance返回的對象,不是此Activiyt實例。
所以原Activity被recreate后,會生成新的Activity,新的FragmentManager,但是是舊的Fragment對象,所以它的生命周期會有所不同,它不會有onCreate和onDestroy,不會被FragmentManager從mActive中刪除,因為在Activity onDestroy時需要被retainNonConfig保存下來。
當新Activity onCreate后會調用FragmentManager的restoreAllState,在此把之前保存在nci對象里的mActive重新取出來,一個一個賦值給新建的mActive對象,完成nonConfig對象的恢復。
如果Fragment未設置fragment.setRetainInstance(true),默認為false
則走原有流程。
宿主FragmentActivity從后臺恢復時由于內存不足已經被kill
眾所周知Activity的onSaveInstanceState在onPause之后在onStop之前,所以當Activity在被放到后臺即onStop前會調用onSaveInstanceState,在此函數中調用了FragmentManager.saveAllState,主要存儲FragmentManager的三個數mActive、mAdded、mBackStack的內容到FragmentManagerState中,mActive中保存了上述的FragmentState。當Activity被重新創建調用onCreate時會得到剛剛保存的savedInstanceState,再通過這個savedInstanceState獲得剛保存的FragmentManagerState,去創建一個新的FragmentManager對象,去重新生成Fragment。此時恢復的FragmentActivity、FragmentManager、Fragment都是新的實例。
此時不管fragment是否setRetainInstance(true),Fragment實例都會重新被創建,原因一:retainNonConfig是在Activity在onDestroy被保存的;原因二:只有被relaunch的activity在destroy時才會在ActivityThread代碼中被調用retainNonConfig去通知Activity返回需要保存實例,其他的destroy不會。
4.3 補充
3.0后處理旋轉屏幕ConfigurationChanged
3.0后需要配置screenSize來處理旋轉屏幕ConfigurationChanged
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize">
如果配置了這個屬性,旋轉屏幕,Activity只會回調onConfigurationChanged,不會調用其他任何生命周期函數,當然也不會被重新生成實例,FragmentManager實例、Fragment實例都是不會發生變化的。
5 小細節
remove fragment
如果Fragment通過layout.xml方式加入到Activity中,被FragmentManager進行remove或者replace操作后,Fragment實例在FragmentManager中被刪去,而Fragment內對應的view沒有被賦值mContainerView,所以內部的view沒有被移除,導致界面一直存在,此時這個Fragment已經被detach,如果再對它調用getActivity將返回null。
add fragment
如果Activity沒有處理screenSize的onConfigurationChanged,那么此Activity將被recreate。在重新調用Activity onCreate時,FragmentManager的mActive和mAdd內的Fragment被重新創建,如果此時在Activity的onCreate重新add一個Fragment,那么就會出現兩個Fragment的情況。
如何解決?在添加前先檢查下FragmentManager內是否存在此Fragment,不存在再添加即可,代碼如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(v);
BlankFragment blankFragment = (BlankFragment) getSupportFragmentManager().findFragmentByTag("BlankFragmentTag");
if ( blankFragment == null ){
blankFragment = new BlankFragment();
blankFragment.setUserVisibleHint(false);
Bundle bundle = new Bundle();
bundle.putInt("data",-1);
blankFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.container, blankFragment, "BlankFragmentTag").commit();
}
}
hide fragment
因為hide狀態沒有被保存下來,在recreate或者內存不足重啟Activity的情況下,原來被hide的Fragment將被重新show,所以得注意這個問題。
replace fragment
目前的版本存在一個bug,一個ViewGroup加了一個以上Fragment后,FragmentManager去replace此ViewGroup內的Fragement無法正確replace,原因是在ArrayList for循環里做了ArrayList remove操作,目前還沒修復。
addBackStack情況下,remove Fragment后Fragment還繼續存在mActive中
由于remove后有可能popBackStack(),所以mAdd內被刪除后,mActive內還保存著此Fragment引用,此時findFragmentById或者findFragmentByTag都可以找到這個Fragment。同理,setRetainInstance(true)的Fragment被remove后也存在在mActive中。
DialogFragment中的onCreateView
同一個activity中的fragment和DialogFragment用onCreateView中所帶的參數inflater去inflate view,view.getContext()返回值是不同的。。前者是此activiy;后者返回的是ContenxThemeWrapper,內部wrap了activity。原因是dialog要創建新的context存放對應的theme去inflate view。
6 reference
本文所涉及源碼版本為
compile 'com.android.support:support-v4:23.1.0'