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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容