2020-03-11-Android Fragment原理

最近刚好遇到了一个问题,新需求快稳省测试发现,应用Home键回到桌面后,电流没有回归,抓了一份systrace,发现ui进程还在跟SurfaceFlinger通信,有animation动画和binder通信,应该是一个VSync信号就有一次通信,花了一天时间才找出来,是同事在Fragment的onViewCreated方法里,通过listParent去加载布局,布局里有个动画的控件,而这个view是没有正常onPause的,所以后台一直在加载动画。
踩坑之后,决定研究一下Fragment的原理。

相关类

主要有四个类,FragmentManager是个抽象类,实现在FragmentManagerImpl;
FragmentTransaction也是个抽象类,实现类是BackStackRecord。


Fragment原理 (3).jpg

Fragment的添加

第一种方法是直接将fragment添加到布局中。

    <fragment
        android:name="world.one.com.newworld.fragment.NewFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

如果在NewFragment的onCreateView中打印堆栈,会看到

    ----world.one.com.newworld.fragment.NewFragment.onCreateView(NewFragment.java:19)
    ----android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
    ----android.support.v4.app.FragmentManagerImpl.ensureInflatedFragmentView(FragmentManager.java:1689)
    ----android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1431)
    ----android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1684)
    ----android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1930)
    ----android.support.v4.app.FragmentManagerImpl.onCreateView(FragmentManager.java:3745)
    ----android.support.v4.app.FragmentController.onCreateView(FragmentController.java:120)
    ----android.support.v4.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:405)
    ----android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:387)

第二种方法是动态替换,将布局中的某个view替换成fragment

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new NewFragment()).commit();

虽然只有一行代码,但是调用的过程是很复杂的。

    ----world.one.com.newworld.fragment.NewFragment.onCreateView(NewFragment.java:19)
    ----android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
    ----android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
    ----android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
    ----android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
    ----android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
    ----android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
    ----android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
    ----android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
    ----android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
    ----android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3273)
    ----android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3229)
    ----android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:201)
    ----android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:620)
    ----android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
Fragment.jpg

先看下add方法添加一个Fragment到布局的过程。实际上是通过doAddOp方法实现的,需要注意的是注释1处传入的opcmd是OP_ADD,需要跟replace方法区分。

    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
        doAddOp(containerViewId, fragment, tag, OP_ADD);//1
        return this;
    }

add方法通过doAddOp将fragment封装成一个Op对象。
1.将fragmentManager指定为当前manager;
2.如果tag存在,将fragment的tag指定为传入的tag;
3.将fragment的ContainerId和FragmentId都指定为传入的containerViewId;
4.将fragment和opcmd封装成一个Op对象,执行addOp。

    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
            final Class fragmentClass = fragment.getClass();
            final int modifiers = fragmentClass.getModifiers();
            if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                    || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers)))) {
                throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                        + " must be a public static class to be  properly recreated from"
                        + " instance state.");
            }
        }
        fragment.mFragmentManager = mManager;//1

        if (tag != null) {
            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
                throw new IllegalStateException("Can't change tag of fragment "
                        + fragment + ": was " + fragment.mTag
                        + " now " + tag);
            }
            fragment.mTag = tag;//2
        }

        if (containerViewId != 0) {
            if (containerViewId == View.NO_ID) {
                throw new IllegalArgumentException("Can't add fragment "
                        + fragment + " with tag " + tag + " to container view with no id");
            }
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;//3
        }

        addOp(new Op(opcmd, fragment));//4
    }

BackStackRecord内部维持了一个ArrayList列表mOps,用来保存fragment封装后的Op对象。

    void addOp(Op op) {
        mOps.add(op);
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
    }

replace方法跟add类似,都是通过doAddOp实现的,只是参数不同。注释1处传入的opcmd是OP_REPLACE。

    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
        if (containerViewId == 0) {
            throw new IllegalArgumentException("Must use non-zero containerViewId");
        }

        doAddOp(containerViewId, fragment, tag, OP_REPLACE);//1
        return this;
    }

其他操作还有remove,hide,show等,实际上都是通过addOp方法往ArrayList中插入一个Op对象,只是它们的opcmd参数不同。

    public FragmentTransaction remove(Fragment fragment) {
        addOp(new Op(OP_REMOVE, fragment));

        return this;
    }
    public FragmentTransaction hide(Fragment fragment) {
        addOp(new Op(OP_HIDE, fragment));

        return this;
    }
    public FragmentTransaction show(Fragment fragment) {
        addOp(new Op(OP_SHOW, fragment));

        return this;
    }
    public FragmentTransaction detach(Fragment fragment) {
        addOp(new Op(OP_DETACH, fragment));

        return this;
    }
    public FragmentTransaction attach(Fragment fragment) {
        addOp(new Op(OP_ATTACH, fragment));

        return this;
    }

不管是什么操作,add,replace或remove,最后一步的操作是commit

    public int commit() {
        return commitInternal(false);
    }
    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) {
            throw new IllegalStateException("commit already called");
        }
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
            dump("  ", null, pw, null);
            pw.flush();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction.
                    return;
                }
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }

最终实现是在executeOps,这里遍历mOps数组,根据cmd对每个数组执行操作。

    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;
            if (f != null) {
                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;
                case OP_SET_PRIMARY_NAV:
                    mManager.setPrimaryNavigationFragment(f);
                    break;
                case OP_UNSET_PRIMARY_NAV:
                    mManager.setPrimaryNavigationFragment(null);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
            }
            if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) {
                mManager.moveFragmentToExpectedState(f);
            }
        }
        if (!mReorderingAllowed) {
            // Added fragments are added at the end to comply with prior behavior.
            mManager.moveToState(mManager.mCurState, true);
        }
    }

onCreateView和onViewCreated的区别

首先,它们在时序上是有先后的,onCreateView先执行,然后是onViewCreated。

参考

https://blog.csdn.net/u011240877/article/details/78132990
https://xiazdong.github.io/2017/06/15/android-fragment/

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

推荐阅读更多精彩内容