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

Posted 薛瑄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fragment has not been attached yet 解决方法及源码详解相关的知识,希望对你有一定的参考价值。

前言

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

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

推荐一下我维护的fragment库,轻松实现单activity+多fragment 的框架,https://github.com/JantHsueh/Fragmentation

报错

通俗的讲,就是在要使用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,图中只展示S2 的mHost数据

那么S2,L2 的mHost 是在哪里赋值的呢?为什么S1,L1的mHost 没有被赋值

在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.以上是关于Fragment has not been attached yet 解决方法及源码详解的主要内容,如果未能解决你的问题,请参考以下文章

lumen----------A facade root has not been set.

Coinitialize has not been called 错误信息

Hangfire JobStorage.Current property value has not been initialized

React NativeInvariant Violation: Application AwesomeProject has not been registered

System.ServiceProcess.TimeoutException: Time out has expired and the operation has not been complete

微信小程序 uniapp has not been registered yet.报错 解决方案