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