Android-Fragment简要分析

注意:文章对Fragment源码的分析基于support v4的Fragment包,版本号为25.3.1

Fragment相关类UML图

1008428-b84d45b1ba19d73d.jpg

support包中对负责管理Fragment生命是FragmentActivity,v7包的AppCompatActivity也是继承于它。
FragmentActivity管理Fragment是通过它内部mFragments的变量,mFragments类型为FragmentController.

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

FragmentActivity传递自身生命状态、状态保存、恢复都是通过调用mFragments相应的方法进行处理;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.attachHost(null /*parent*/);
 
    super.onCreate(savedInstanceState);
 
    //...
    mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
 
    //...
    mFragments.dispatchCreate();
}
 
public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
}

而FragmentController本身只是简单的将相应请求转发给FragmentManagerImpl,只是起到了桥梁沟通作用。

FragmentActivity创建mFragments时传递了一个HostCallbacks内部类实例,这个callback对象给Fragment提供了获取Context,启动Activity的能力。

Fragment

Fragment主要是由Attr、View、State构成,
Attr:固有属性,Fragment基本信息,基本不会随生命周期变化。

Bundle mArguments;  //构造参数
boolean mFromLayout; //是否从layout文件中创建
String mTag;
...

View:Fragment管理View所依赖的相关成员变量。

// The parent container of the fragment after dynamically added to UI.
ViewGroup mContainer;
 
// The View generated for this fragment.
View mView;
 
// The real inner view that will save/restore state.
View mInnerView;

State:Fragment的状态,管理Fragment的显示、生命周期。

int mState = INITIALIZING; //生命周期状态
boolean mAdded; //是否被添加
boolean mRemoving; //是否被移除
boolean mHidden;// 是否被隐藏
boolean mDetached; //是否已经分离
 
// mState取值如下
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.

Fragment的生命周期方法,onAttach, onCreate, onCreateView...都是在mState状态变化中进行回调的。

Fragment的事务:FragmentTransaction、BackStackRecord

添加一个Fragment,我们一般使用如下代码:

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contaniner, fragment);
transaction.commit();

FragmentTransaction表示一个事务,提供了如add、remove、attach、detach、show、hide、replace等接口操控Fragment。它的具体实现类是BackStackRecord,它的接口实现如下:

@Override
public FragmentTransaction add(Fragment fragment, String tag) {
    doAddOp(0, fragment, tag, OP_ADD);
    return this;
}
 
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    ...
    Op op = new Op();
    op.cmd = opcmd;
    op.fragment = fragment;
    addOp(op);
}
 
void addOp(Op op) {
    mOps.add(op);
    op.enterAnim = mEnterAnim;
    op.exitAnim = mExitAnim;
    op.popEnterAnim = mPopEnterAnim;
    op.popExitAnim = mPopExitAnim;
}
 
@Override
public FragmentTransaction remove(Fragment fragment) {
    Op op = new Op();
    op.cmd = OP_REMOVE;
    op.fragment = fragment;
    addOp(op);
    return this;
}
...

可以看到我们调用的add、remove等方法最后都是生成一个Op对象保存在mOps列表中,Op是Operator简写表示操作,Op的cmd属性则区分操作的类型,cmd的值取如下几种:

static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;

上面操作完毕后,最后是commit事务了

@Override
public int commit() {
    return commitInternal(false);
}
 
@Override
public int commitAllowingStateLoss() {
    return commitInternal(true);
}
 
@Override
public void commitNow() {
    ...
    // 直接开始执行事务
    mManager.execSingleAction(this, false);
}
 
@Override
public void commitNowAllowingStateLoss() {
    ...
    // 直接开始执行事务
    mManager.execSingleAction(this, true);
}
 
int commitInternal(boolean allowStateLoss) {
    ...
    // 事务入对象,等待下一个Handler回调执行
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}

*AllowingStateLoss表示是否允许状态丢失,如果不允许但是当前Activity状态已经触发保存了调用会抛出异常;commit、commitNow的区别则是提交事务等待下个主线程Handler回调执行还是立即执行,除了执行时机不一样外,两者最后触发的事务执行逻辑是一样的。

事务执行逻辑依然在BackStackRecord中:

void executeOps() {
    final int numOps = mOps.size();
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.fragment;
        f.setNextTransition(mTransition, mTransitionStyle);
        switch (op.cmd) {
            case OP_ADD:
                f.setNextAnim(op.enterAnim);
                mManager.addFragment(f, false);
                break;
            case OP_REMOVE:
                f.setNextAnim(op.exitAnim);
                mManager.removeFragment(f);
                break;
            case OP_HIDE:
                f.setNextAnim(op.exitAnim);
                mManager.hideFragment(f);
                break;
            case OP_SHOW:
                f.setNextAnim(op.enterAnim);
                mManager.showFragment(f);
                break;
            case OP_DETACH:
                f.setNextAnim(op.exitAnim);
                mManager.detachFragment(f);
                break;
            case OP_ATTACH:
                f.setNextAnim(op.enterAnim);
                mManager.attachFragment(f);
                break;
            default:
                throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
        }
        if (!mAllowOptimization && op.cmd != OP_ADD) {
            mManager.moveFragmentToExpectedState(f);
        }
    }
    if (!mAllowOptimization) {
        // Added fragments are added at the end to comply with prior behavior.
        mManager.moveToState(mManager.mCurState, true);
    }
}

执行逻辑是遍历所有添加的操作,执行相应的FragmentManage方法,最后将Fragment的状态移动的相应的状态。

Fragment状态变迁:moveToState

Fragment添加到FragmentMange中后,要将它移动到相应的状态,使Fragment正确的显示、交互。

FragmentManage的成员变量mCurState存储着当前Activity状态对应的Fragment状态值,它的取值范围如同Fragment。Activity的状态发生变化时都会触发相应方法修改mCurState,并将所有的active 的Fragment移动到相应的状态上。

public void dispatchCreate() {
    mStateSaved = false;
    moveToState(Fragment.CREATED, false);
}
 
public void dispatchActivityCreated() {
    mStateSaved = false;
    moveToState(Fragment.ACTIVITY_CREATED, false);
}
 
public void dispatchStart() {
    mStateSaved = false;
    moveToState(Fragment.STARTED, false);
}
 
public void dispatchResume() {
    mStateSaved = false;
    moveToState(Fragment.RESUMED, false);
}
 
public void dispatchPause() {
    moveToState(Fragment.STARTED, false);
}
 
public void dispatchStop() {
    // See saveAllState() for the explanation of this.  We do this for
    // all platform versions, to keep our behavior more consistent between
    // them.
    mStateSaved = true;
 
    moveToState(Fragment.STOPPED, false);
}
 
public void dispatchReallyStop() {
    moveToState(Fragment.ACTIVITY_CREATED, false);
}
 
public void dispatchDestroyView() {
    moveToState(Fragment.CREATED, false);
}
 
public void dispatchDestroy() {
    mDestroyed = true;
    execPendingActions();
    moveToState(Fragment.INITIALIZING, false);
    mHost = null;
    mContainer = null;
    mParent = null;
}

FragmentManage的moveToState方法就负责了将自己管理的所有Fragment的状态移动到对应的状态上,当mCurState变化,或者Fragment操作触发都会调用该方法。

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    // Fragments that are not currently added will sit in the onCreate() state.
    ...
    if (f.mState < newState) {
        // For fragments that are created from a layout, when restoring from
        // state we don't want to allow them to be created until they are
        // being reloaded from the layout.
        ...
        switch (f.mState) {
            case Fragment.INITIALIZING:
                ...
            case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
                   ...
                }
            case Fragment.ACTIVITY_CREATED:
            case Fragment.STOPPED:
                if (newState > Fragment.STOPPED) {
                    if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                    f.performStart();
                }
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                    f.performResume();
                    f.mSavedFragmentState = null;
                    f.mSavedViewState = null;
                }
        }
    } else if (f.mState > newState) {
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
                    f.performPause();
                }
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
                    f.performStop();
                }
            case Fragment.STOPPED:
                if (newState < Fragment.STOPPED) {
                    if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f);
                    f.performReallyStop();
                }
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                   ...
                }
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    ...
                }
        }
    }
 
    ...
}

fragment的state取值,为前面提到的七中状态,其中最低值是INITIALIZING状态,代表fragment刚创建,还未被add, 最高状态值是RESUMED,代表fragment处于前台。 所以moveToState内部分两条线,状态跃升,和状态降低,里面各有一个switch判断,注意到switch里每个case都没有break,这意味着,状态可以持续变迁,比如从INITIALIZING,一直跃升到RESUMED,将每个case都走一遍,每次case语句内,都会改变state的值。

Untitled.png

Fragment状态保存、恢复

主要实现代码是FragmentManageImpl的saveAllState()和restoreAllState()方法。

FragmentActivity的onSaveInstance()方法中会间接调用FragmentManageImpl的saveAllState()方法,而onCreate(Bundle)中会间接调用restoreAllState()。在Fragment的onCreate(Bundle)等初始化生命方法中会传入保存状态的Bundle,以供开发者恢复存储的数据。而Fragment中view的状态的恢复则是在回调了onCreateView()后得到开发者返回的view,手动触发view状态的恢复:

// FragmentManageImpl.class
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
  ...
  if(f.mState < newState) { // 状态升
     ...
    switch(f.mState) {
      ...
      case Fragment.CREATED:
        ...
        f.mView = f.performCreateView(f.performGetLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
        ...
        f.performActivityCreated(f.mSavedFragmentState);
        ...
        if (f.mView != null) {
          f.restoreViewState(f.mSavedFragmentState);
        }
        f.mSavedFragmentState = null;
    }
    ...
  }
  ...
}

// Fragment.class
  final void restoreViewState(Bundle savedInstanceState) {
        if (mSavedViewState != null) {
            mInnerView.restoreHierarchyState(mSavedViewState);
            mSavedViewState = null;
        }
        mCalled = false;
        onViewStateRestored(savedInstanceState);
        if (!mCalled) {
            throw new SuperNotCalledException("Fragment " + this
                    + " did not call through to super.onViewStateRestored()");
        }
    }

Fragment常见错误:

可以参考:https://www.jianshu.com/p/d9143a92ad94

getActivity空指针错误

状态没保存、值丢失

Can not perform this action after onSaveInstanceState异常

Fragment类必须提供无参构造方法

Fragment重叠显示异常

其他

Fragment中还有回退栈、动画、Android5.0中的共享元素及Activity动画、Fragment.retainInstance属性、LoaderManager、ChildFragmentManage等知识点这里先不讨论了。

参考
从源码角度剖析Fragment核心知识点

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

推荐阅读更多精彩内容