Fragment源碼閱讀筆記

ReyIronside 8年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

來自: http://www.jianshu.com/p/bd4a8be309c8

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();

}</pre>

對應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代碼如下:

</div>

// 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); ... }</pre>

在上述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)</pre>

舉個例子,當FragmentActivity已經onResume后,即FragmentActivity已經顯示在屏幕中,此時FragmentActivity中onResume已經調用了mFragmentManager的dispatchResume函數,即

// android.support.v4.app.FragmentManager

public void dispatchResume() { mStateSaved = false; moveToState(Fragment.RESUMED, false); }</pre>

通過層層調用,進入到了上述第三個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); ... } } ... } }</pre>

先忽略現有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); ... }</pre>

所以執行到了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; }</pre>

同理從RESUMED狀態被destroy,需要從5遷移到0,執行上述函數f.mState > newState部分的邏輯,一步一步回到INITIALIZING狀態。

當然如果宿主Activity與fragement同時被銷毀,fragement會接收到FragmentActivity對應的生命周期dispatch,從5到4,從4到3,從3到2,從2到1,從1到0即可完成狀態遷移。

3 生命周期

fragment_lifecycle.png

先來一張官方文檔的圖,第二章已經介紹了狀態遷移,對應的狀態改變會調用對應的生命周期回調,調用時機已經非常清晰,說一下幾個注意點:

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()); ... } ... } ... }</pre>

onActivityCreated是一個什么狀態?

FragmentActivity在調用onCreate開始的時候調用FragmentManager實例的dispatchCreate函數,在FragmentActivity在調用onCreate結束的時候會調用dispatchActivityCreated,即通知fragment的activity已經調用onCreate完畢。

</div>

為什么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; }</pre>

- ViewState

Fragment內部管理的view的狀態,我們知道view自身有一套狀態保存的機制,通過根節點的view一層一層dispatch出去(dispatchSaveInstanceState、dispatchRestoreInstanceState)觸發保存和恢復先前的狀態。這種恢復屬于view被新建實例后恢復原來的狀態,比如EditText選中了一段文字,旋轉屏幕重新創建view實例,會重新focus,重新選中剛剛所選的那段文字。

</div>

而由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); } ... }</pre>

- mUserVisibleHint

用這個標記可以控制Fragment是否延遲執行mLoaderManager內的任務,即如果mUserVisibleHint為false,這個Fragment在需要遷移到大于STOPPED的狀態時先忽略,當所有其他mUserVisibleHint為true的Fragment內的runningLoader執行完成,再遷移到FragmentManager現有狀態。

</div>

那為什么是停止在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;</pre>

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; }</pre>

我們來看看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;

}</pre>

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,所以得注意這個問題。

</div>

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'
</div>

 本文由用戶 ReyIronside 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!