Fragment源码中的七把利刃(上)

作者: 豆沙包67 | 来源:发表于2016-10-24 22:27 被阅读1513次

Fragment,一个因爱生恨的组件。兼容大屏,适配多尺寸,持久化状态,作为加载器,Fragment都行。既然如此通用,那就用起来。随着项目UI越演复杂,功能需求日渐增多,突然发现出现了很多无可理喻的bug,而且都是跟Fragment密切相关的。何以解忧吗,唯有源码。

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("Fragment does not have a view");
            }
            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("Already attached");
    mHost = host;
    mContainer = container;
    mParent = parent;
}

中赋值,attachController会在2.attachHost()中调用,而在dispatchDestroy()中置空。mHost 为null时容易导致

if (mHost == null && newState != Fragment.INITIALIZING) {    throw new IllegalStateException("No host");}

回到重点。方法moveToState()会陆续调用Fragment的生命周期方法。一直不停初始化到Fragment.performResume()方法,此时Fragment.mState置为Fragment.RESUMED(此过程略过,感兴趣请自行阅读源码)。

:moveToState快速理解:
f.mState < newState:是fragment创建;f.mState > 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 && mParentFragment != null) {
        throw new IllegalStateException(
                "Can't retain fragements that are nested in other fragments");
    }
    mRetainInstance = retain;
}

不能放在嵌套Fragment中。

看一眼FragmentActivity,在持久化状态时调用

@Override
public final Object onRetainNonConfigurationInstance() {
    //...
    List<Fragment> fragments = mFragments.retainNonConfig();
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.fragments = fragments;
    //...
    return nci;
}

跟踪到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;
                f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
                if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + 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,版本兼容见Fragment源码中的七把利刃(下)

6.0 小结

Fragment的创建,销毁,持久化,是三把利刃,可以很好地解决问题,万一出现问题,那就要当心刀刃的方向是不是自己。

相关文章

网友评论

本文标题:Fragment源码中的七把利刃(上)

本文链接:https://www.haomeiwen.com/subject/mrhzyttx.html