Fragment has not been attached yet 解决方法及源码详解

前言

在使用Fragment的时候,在内存重启的后,很容易出现一些难以预期的bug,下面将继续一边分析源码,一边看看这个bug是怎么产生的。

这个报错的原因,可能和你的情况并不尽然相同。但是你可以通过对源码的理解,来加深对fragment的认识,从而能更优雅的解决问题。

报错

通俗的讲,就是在要使用Fragment 的 mHost 变量的时候,这个 变量为空。导致下面的报错

2019-12-30 09:39:55.755 28136-28136/com.icisoo.xw.staging E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2019-12-30 09:39:55.761 28136-28136/com.icisoo.xw.staging E/MessageQueue-JNI: java.lang.IllegalStateException: Fragment has not been attached yet.
        at androidx.fragment.app.Fragment.instantiateChildFragmentManager(Fragment.java:2383)
        at androidx.fragment.app.Fragment.getChildFragmentManager(Fragment.java:845)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate.getChildFragmentRecords(DebugStackDelegate.java:183)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate.addDebugFragmentRecord(DebugStackDelegate.java:211)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate.getFragmentRecords(DebugStackDelegate.java:153)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate.showFragmentStackHierarchyView(DebugStackDelegate.java:101)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate$1.onClick(DebugStackDelegate.java:67)
        at android.view.View.performClick(View.java:6597)
        at me.yokeyword.fragmentation.debug.DebugStackDelegate$StackViewTouchListener.onTouch(DebugStackDelegate.java:257)
        at android.view.View.dispatchTouchEvent(View.java:12509)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)
        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
        at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
        at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
        at me.yokeyword.fragmentation.SupportActivity.dispatchTouchEvent(SupportActivity.java:59)
        at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
        at android.view.View.dispatchPointerEvent(View.java:12752)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5106)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4909)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7092)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7061)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7022)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7195)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:326)
        at android.os.Looper.loop(Looper.java:160)
        at android.app.ActivityThread.main(ActivityThread.java:6718)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    	at 

重现步骤

为了方便描述,下面SplashFragment 实例 简称S,LoginFragment 实例简称 L。第一次打开APP,通过replace(S)启动S后,会add(L)hide(S)

如何模拟内存重启,查看这篇博客

1、第一次打开app,有 S1,L1
2、点击home键(模拟内存重启,系统会保存S1,L1),
3、打开app,有S2,L2,S1,L1 (S1,L1 是系统自动恢复的)
4、点击home键(模拟内存重启,系统会保存S2,L2,S1,L1)
5、打开app,有S3,L3,S2,L2,S1,L1(S2,L2,S1,L1 是系统自动恢复的)

这时候S1和L1的mHost 为空,导致上面这个错误

当然这样的启动过程,是有问题的,同学们也很容易想出解决办法。我们通过源码来深入分析一下,为什么会出现这个问题。

源码分析

通过追溯源码,发现在第二次内存重启后,出现S1 L1 的mHost 为空的问题。
在这里插入图片描述

到当下断点的看下更深的函数调用
在这里插入图片描述

这里就是重新步骤中的第五步,其中 1和 2 的实例的mHost 为空,如下图(图中只展示1的变量情况):

在这里插入图片描述

通过不断调试,发现在重新步骤的第五步,系统恢复S2,L2,S1,L1,

在这里插入图片描述

这时候S2,L2,S1,L1 中的mHost 值都为null,图中只展示L2 的mHost数据

在这里插入图片描述

但是我们看到的是只有S1,L1 为空,那么S2,L2 是在哪里赋值的呢?

在onCreate()函数执行的时候,调用了 dispatchStateChange(Fragment.CREATED); 最终调用下图中的moveToState() 来把mAdded 中的fragment 移动到指定的生命周期状态
在这里插入图片描述
moveFragmentToExpectedState中判断要置为哪种初始状态,在下图中的moveToState 中的函数设置了mHost的值。也就是说在初始化中,只有在mAdded中的Fragment 才会设置mHost。在恢复数据时,S1,L1 没有添加到mAdded,也就是说上次保存数据的时候,S1,L1没有被保存在mAdded中
在这里插入图片描述

来看看上一次保存fragment 实例时(重现步骤第3步)是什么样的

mActive 中有S2,L2,S1,L1 ,mAdded 中有S2,L2
在这里插入图片描述
下面是上图中的 saveAllState()的源码

    Parcelable saveAllState() {
        // Make sure all pending operations have now been executed to get
        // our state update-to-date.
        //在保存状态前,保证所以的Transactions都已经执行,动画都已经结束(因为动画结束也会影响fragment的状态,例如animateRemoveFragment(),)。
        //在onSaveInstance后,不能提交事务.共同保证了,最终保存fragment状态的将不再改变
        forcePostponedTransactions();
        endAnimatingAwayFragments();
        execPendingActions();

        mStateSaved = true;
        mSavedNonConfig = null;

        if (mActive == null || mActive.size() <= 0) {
            return null;
        }

        // First collect all active fragments.
        int N = mActive.size();
        //FragmentState一个序列化的用来保存fragment的类
        FragmentState[] active = new FragmentState[N];
        boolean haveFragments = false;
        //遍历mActive中的每一个 fragment
        for (int i=0; i<N; i++) {
            Fragment f = mActive.valueAt(i);
            if (f != null) {
                if (f.mIndex < 0) {
                    throwException(new IllegalStateException(
                            "Failure saving state: active " + f
                            + " has cleared index: " + f.mIndex));
                }

                haveFragments = true;
				//把fragment 转为FragmentState 
                FragmentState fs = new FragmentState(f);
                //存储在数组active中,这个数组最终被保存
                active[i] = fs;

                if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                    fs.mSavedFragmentState = saveFragmentBasicState(f);

                    if (f.mTarget != null) {
                        if (f.mTarget.mIndex < 0) {
                            throwException(new IllegalStateException(
                                    "Failure saving state: " + f
                                    + " has target not in fragment manager: " + f.mTarget));
                        }
                        if (fs.mSavedFragmentState == null) {
                            fs.mSavedFragmentState = new Bundle();
                        }
                        putFragment(fs.mSavedFragmentState,
                                FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
                        if (f.mTargetRequestCode != 0) {
                            fs.mSavedFragmentState.putInt(
                                    FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
                                    f.mTargetRequestCode);
                        }
                    }

                } else {
                    fs.mSavedFragmentState = f.mSavedFragmentState;
                }
            }
        }


        int[] added = null;
        BackStackState[] backStack = null;

        // Build list of currently added fragments.
        //因为mActive 是mAdded的超级,所以这里保存mAdded,就只保存了索引值
        N = mAdded.size();
        if (N > 0) {
            added = new int[N];
            for (int i = 0; i < N; i++) {
                added[i] = mAdded.get(i).mIndex;
                if (added[i] < 0) {
                    throwException(new IllegalStateException(
                            "Failure saving state: active " + mAdded.get(i)
                            + " has cleared index: " + added[i]));
                }
                if (DEBUG) {
                    Log.v(TAG, "saveAllState: adding fragment #" + i
                            + ": " + mAdded.get(i));
                }
            }
        }

        // Now save back stack.
        //把mBackStack信息保存在BackStackState中
        if (mBackStack != null) {
            N = mBackStack.size();
            if (N > 0) {
                backStack = new BackStackState[N];
                for (int i=0; i<N; i++) {
                    backStack[i] = new BackStackState(mBackStack.get(i));
                    if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
                            + ": " + mBackStack.get(i));
                }
            }
        }
		//保存active、added、backStack的序列化类
        FragmentManagerState fms = new FragmentManagerState();
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        if (mPrimaryNav != null) {
            fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
        }
        fms.mNextFragmentIndex = mNextFragmentIndex;
        saveNonConfig();
        return fms;
    }

保存数据搞明白了,恢复数据就很简单,它的逆操作。这里不再详细展开。

那么问题来了,为什么保存的时候,mAdded中只有两个本次添加的fragment呢,系统恢复的fragment为什么没在mAdded中呢?

此时我有两种思路:

1、到上上次保存数据看看。可能上上次保存数据时候,mAdded中没有S1,L1,导致上次恢复数据的时候,没有S1,L1。这种可能应该不大,毕竟是系统的源码,不可能出现这个错误吧。

2、在上次恢复数据后(对应重新步骤3),有恢复S1,L1到mAdded中,但是由于某种操作,给删除了。

经过调试发现,是第二种问题,那么下面来详细描述一下,是什么导致mAdded中的S1,L1的被删除

在上次恢复数据后(对应重新步骤3),S1,L1的mHost 也是被赋值过的(与上面分析的S2,L2是一样的过程),只是后来mAdded中的S1,L1被删除了

在这里插入图片描述

经过不断调试发现,事务中执行了S1,L1的remove()操作,原来是S2使用replace() ,系统优化Ops 导致 S1,L1被remove,下面通过源码分析一下,这个remove行为是如何产生的

打开APP时,业务代码会先使用replace()来启动S(这里指S2),fragment在处理这个事务的时候,在函数expandPos()对操作进行优化,包括replace

在这里插入图片描述

在这里插入图片描述

下图可以看到replace S2,命令 最终边转变为,remove L1 ,S1,add S2,所以

这里顺便插个题外话,调试代码中op是mOps中的第 0 索引的值,但是在代码运行过程中,mOps 第0索引的值,发生变化,op也发生了变化。于是出现下图中,debugger中的局部变量op和代码中对应的op的变量内容不一样

在这里插入图片描述
下面是上图的代码,下面进行详细分析

   // 对mOps操作 进行预处理,也算是一种对op操作优化的操作
    Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
        for (int opNum = 0; opNum < mOps.size(); opNum++) {
           // 遍历mOps 中所有的操作。例如:add,show,hide,replace 等这些事务中的操作,都会保存在mOps 
            final Op op = mOps.get(opNum);
            switch (op.cmd) {
                case OP_ADD:
                case OP_ATTACH:
                    added.add(op.fragment);
                    break;
                case OP_REMOVE:
                case OP_DETACH: {
                    added.remove(op.fragment);
                    if (op.fragment == oldPrimaryNav) {
                        mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
                        opNum++;
                        oldPrimaryNav = null;
                    }
                }
                break;
                case OP_REPLACE: {
                    //获取该操作对应的fragment,例如:add(f1),这里的op.fragment 就是f1
                    final Fragment f = op.fragment;
                    //获取f的容器id
                    final int containerId = f.mContainerId;
                    //是否已经加入到added,这个list中保存所有存活的fragment(没有remove和detached ),mActive包含mAdded
                    boolean alreadyAdded = false;
                    //遍历added中的fragment
                    for (int i = added.size() - 1; i >= 0; i--) {
                        final Fragment old = added.get(i);
                        //如果是针对同一容器进行replace
                        if (old.mContainerId == containerId) {
                            if (old == f) {
                                //例如:replace(f1),f1已经在madded中,也就是说f1之前被添加过
                                alreadyAdded = true;
                            } else {
                                // This is duplicated from above since we only make
                                // a single pass for expanding ops. Unset any outgoing primary nav.
                                if (old == oldPrimaryNav) {
                                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
                                    opNum++;
                                    oldPrimaryNav = null;
                                }
                                //删除相同容器id的fragment
                                final Op removeOp = new Op(OP_REMOVE, old);
                                removeOp.enterAnim = op.enterAnim;
                                removeOp.popEnterAnim = op.popEnterAnim;
                                removeOp.exitAnim = op.exitAnim;
                                removeOp.popExitAnim = op.popExitAnim;
                                //增加一个新的删除操作
                                mOps.add(opNum, removeOp);
                                //从added list中删除这个具有相同容器id的fragment
                                added.remove(old);
                                //操作的数量++
                                opNum++;
                            }
                        }
                    }
                    
                    if (alreadyAdded) {
                       //如果已经添加过,删除replace操作
                        //这里删除的是replace的操作,因为mOps.add 都是添加在opNum位置,把replace往后挤了一位,
                        //所以opNum++ 后,opNum 就是replace的索引位置
                        mOps.remove(opNum);
                        opNum--;
                    } else {
                        //如果没有添加过,把replace改为add
                        op.cmd = OP_ADD;
                        //添加到added中
                        added.add(f);
                    }
                }
                break;
                case OP_SET_PRIMARY_NAV: {
                    // It's ok if this is null, that means we will restore to no active
                    // primary navigation fragment on a pop.
                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
                    opNum++;
                    // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
                    oldPrimaryNav = op.fragment;
                }
                break;
            }
        }
        return oldPrimaryNav;
    }

这里可以看到,最后removeFragment函数中,调用了mAdder.remove() 把L1 和S1删除了,(下图只展示了L1被删除的断点)
在这里插入图片描述

所以在此时保存数据时,是没有L1和S1,也就是说在恢复数据时,mAdder中也没有L1和S1,也就不会给mHost赋值,导致上面这个错误。

解决方法:

第一种:

初次启动App,加载fragment使用add,而不是用replace,避免退出 已经恢复的其他fragment

第二种:

启动App时,判断是否已经有目标fragment的实例,如果已经有该实例,则不创建新的

发布了242 篇原创文章 · 获赞 775 · 访问量 224万+

猜你喜欢

转载自blog.csdn.net/xx326664162/article/details/103761315