Fragment源碼中的七把利刃

分類:技術 時間:2016-10-25

Fragment,一個因愛生恨的組件。兼容大屏,適配多尺寸,持久化狀態,作為加載器,Fragment都行。既然如此通用,那就用起來。隨著項目UI越演復雜,功能需求日漸增多,突然發現出現了很多無可理喻的bug,而且都是跟Fragment密切相關的。何以解憂嗎,唯有源碼。

源碼版本是android support 23.4.0,Fragment用的是v4包下。

1.0 第一刀 FragmentManager

Fragment的使用分為兩種情況

  • 依附于宿主FragmentActivity
  • 嵌套Fragment

以下情況比較常見,作為例子最好不過。

圖1.簡單嵌套

宿主Activity(只針對FragmentActivity及其子類)嵌套一個父Fragment,在父Fragment下又嵌套三個同級的子Fragment。

思考 如何取到各自的FragmentManager?什么時候需要用哪層的FragmentManager?

1.1 Activity FragmentManager

Activity獲取FragmentManager的方法有

getSupportFragmentManager();

跟蹤源碼后發現指向FragmentHostCallback

final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

FragmentManagerImpl getFragmentManagerImpl() {
    return mFragmentManager;
}

橫向箭頭表示 持有關系

圖2.鏈式持有

1.2 Fragment FragmentManager

Fragment獲取FragmentManager的方法

getFragmentManager();
getChildFragmentManager();

跟蹤第一個方法源碼Fragment.java

FragmentManagerImpl mFragmentManager;

final public FragmentManager getFragmentManager() {
    return mFragmentManager;
}

public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
    //省略代碼
    mInstance.mFragmentManager = host.mFragmentManager;
}

原來還是FragmentHostCallback.mFragmentManager,與宿主Activity相同,得出以下結論:

Activity.getSupportFragmentManager()與Fragment.getFragmentManager相同。

跟蹤第二個方法源碼Fragment.java

// Private fragment manager for child fragments inside of this one.
FragmentManagerImpl mChildFragmentManager;

void instantiateChildFragmentManager() {
    mChildFragmentManager = new FragmentManagerImpl();
    mChildFragmentManager.attachController(mHost, new FragmentContainer() {
        @Override
        @Nullable
        public View onFindViewById(int id) {
            if (mView == null) {
                throw new IllegalStateException(quot;Fragment does not have a viewquot;);
            }
            return mView.findViewById(id);
        }

        @Override
        public boolean onHasView() {
            return (mView != null);
        }
    }, this);
}

先不管FragmentManager如何初始化,可以看出ChildFragmentManager是Fragment私有的。

回到圖1.簡單嵌套中的實際情況。

A.getSupportFragmentManager()與B.getFragmentManager()相同,取到Activity的FragmentManager。

B.getChildFragmentManager與C/D/E.getParentFragment().getChildFramgnetManager()相同,取到FramgmentB的FragmentManager。

C/D/E.getChildFramgnetManager()是沒多大意義的。

2.0 第二刀 Fragment的生命周期

如圖2鏈式持有的類圖我們可以簡單了解這幾個類的關系。

再看一張Fragment創建的時序圖

圖3.Fragment創建的時序圖

創建簡單分為兩步:

  • onCreate,圖中1~7,Fragment.mState置為Fragment.INITIALIZING。
  • onCreateView,圖中8~15,在10.onCreateView()會將狀態置為Fragment.CREATED。

2.1 創建過程的要點

在FragmentActivity創建時就調用

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

給FragmentController.mHost賦值,而不會置空。

反觀FragmentManager是在

public void attachController(FragmentHostCallback host,
        FragmentContainer container, Fragment parent) {
    if (mHost != null) throw new IllegalStateException(quot;Already attachedquot;);
    mHost = host;
    mContainer = container;
    mParent = parent;
}

中賦值,attachController會在2.attachHost()中調用,而在dispatchDestroy()中 置空 。mHost 為null時容易導致

if (mHost == null amp;amp; newState != Fragment.INITIALIZING) {    throw new IllegalStateException(quot;No hostquot;);}

回到重點。方法moveToState()會陸續調用Fragment的生命周期方法。一直不停初始化到Fragment.performResume()方法,此時Fragment.mState置為Fragment.RESUMED(此過程略過,感興趣請自行閱讀源碼)。

注:moveToState快速理解:

f.mState lt; newState:是fragment創建;f.mState gt; newState:是fragment銷毀;而且,switch并沒有break,需要當心。

貼出官方生命周期圖:

圖4.fragment_lifecycle.png

2.2 Fragment的銷毀

隨著FragmentActivity調用

/**
 * Dispatch onPause() to fragments.
 */
@Override
protected void onPause() {
    super.onPause();
    mResumed = false;
    if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
        mHandler.removeMessages(MSG_RESUME_PENDING);
        onResumeFragments();
    }
    mFragments.dispatchPause();
}

經過moveToState()執行了fragment.performPause(),此時Fragment.mState置為Fragment.STARTED。

接著FragmentActivity調用

/**
 * Dispatch onStop() to all fragments.  Ensure all loaders are stopped.
 */
@Override
protected void onStop() {
    super.onStop();

    mStopped = true;
    mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);

    mFragments.dispatchStop();
}

經過moveToState()執行了fragment.performStop(),此時Fragment.mState置為Fragment.STOPPED。

看一下時序圖

圖5.Fragment.onStop時序圖

此時Fragment.mState置為Fragment.ACTIVITY_CREATED。

最后FragmentActivity調用

/**
 * Destroy all fragments and loaders.
 */
@Override
protected void onDestroy() {
    super.onDestroy();

    doReallyStop(false);

    mFragments.dispatchDestroy();
    mFragments.doLoaderDestroy();
}

又重新調用了doReallyStop(false)確保已經走完stop的生命周期,到moveToState后接著走framgnet.performDestroy()和fragment.onDetach()。此時Fragment.mState置為Fragment.INITIALIZING。

生命周期小結:

  • 從源碼來看,Fragment的生命周期完全依賴與FragmentActivity,而且并 不是 相當嚴謹,這也是為什么嵌套Fragment如此容易引發各種版本兼容問題。
  • 未提到動畫的加載,其實會導致很多問題。

3.0 第三刀 Fragment持久化

這句代碼應該寫過無數遍了

setRetainInstance(true);

官方文檔是

控制Activity重建時(如屏幕旋轉)是否會持久化Fragment,只能用在不寸在后臺堆棧中的Framgnet中,如果設置true,生命周期會有變化:不會調用onDestory(),不會調用onCreate(Bundle)...

源碼如下

public void setRetainInstance(boolean retain) {
    if (retain amp;amp; mParentFragment != null) {
        throw new IllegalStateException(
                quot;Can't retain fragements that are nested in other fragmentsquot;);
    }
    mRetainInstance = retain;
}

不能放在嵌套Fragment中。

看一眼FragmentActivity,在持久化狀態時調用

@Override
public final Object onRetainNonConfigurationInstance() {
    //...
    Listlt;Fragmentgt; fragments = mFragments.retainNonConfig();
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.fragments = fragments;
    //...
    return nci;
}

跟蹤到FragmentManager

ArrayListlt;Fragmentgt; retainNonConfig() {
    ArrayListlt;Fragmentgt; fragments = null;
    if (mActive != null) {
        for (int i=0; ilt;mActive.size(); i  ) {
            Fragment f = mActive.get(i);
            if (f != null amp;amp; f.mRetainInstance) {
                if (fragments == null) {
                    fragments = new ArrayListlt;Fragmentgt;();
                }
                fragments.add(f);
                f.mRetaining = true;
                f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
                if (DEBUG) Log.v(TAG, quot;retainNonConfig: keeping retained quot;   f);
            }
        }
    }
    return fragments;
}

從mActive的列表中選出setRetainInstance(true)的Fragment。

再回來看看FragmentActivity.onCreate()

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//...
 NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
 mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
 //...
}

FragmentActivity創建時會重新給這些Fragment賦值。

小結:setRetainInstance(true)略過了Fragment的onCreate和onDestory生命周期,等于由FragmentActivity控制Fragment的

持久化和恢復。

4.0 學到的

  • 委托模式
    FragmentActivity將生命周期與Fragment相關的方法委托給FragmentController,剝離了耦合關系。
  • 代理模式
    FragmentController的構造方法中使用FragmentHostCallback作為抽象,而FragmentActivity.HostCallbacks作為真正實現者。
  • 避免使用復雜的生命周期方法
    盡量不要嵌套Fragment...不要相信...依賴...避免...Fragment的生命周期,能不用就不用吧,可以使用GitHub上的更簡介的三方庫,但是項目中依賴太深的也只能自己踩坑自己填。

5.0 未出鞘之四五六七

Transction,動畫,LoaderManager,版本兼容...各種坑,有時間再填。

6.0 小結

Fragment的創建,銷毀,持久化,是三把利刃,可以很好地解決問題,萬一出現問題,那就要當心刀刃的方向是不是自己。


Tags: 安卓開發

文章來源:http://www.jianshu.com/p/e15c74f86303


ads
ads

相關文章
ads

相關文章

ad