ListView嵌套ViewPager+Fragment引起的Bug
来源:互联网 发布:淘宝买车怎么买 编辑:程序博客网 时间:2024/05/21 22:50
发现问题
之前发现过一次,情景一样,当时是将ListView替换为LinearLayout然后动态添加view解决,这次又发现这个问题,感觉得从根本上找出原因所在,毕竟listview嵌套多层viewpager+fragment场景还是存在很多的(如资产详情带图表切换),报的crash
分析问题
定位问题
根据报错日志No view found for id 0x7f0f03f8
定位源码位置FragmentManager#MoveToState(1051)
` case Fragment.CREATED: if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + f.getResources().getResourceName(f.mContainerId) + ") for fragment " + f)); } } f.mContainer = container; f.mView = f.performCreateView(f.getLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState);`
可以看出fragment创建时时会根据它所记录的容器id去找到之前的容器,比如没有找到就会发生这个Error
单步debug
【onCreate】看出最外层的vpProfitContainer(viewpager)的id就是说找不到的#7f0f03f8
查看Actvity中的管理的Fragments
可以看出actvity持有两个fragment外面tab的fragment他们的容器Id就是vpProfitContainer(#7f0f03f8)
在Onresume回来时
debug到vpProfitContainer.setAdapter(profitTrendsPageAdapter);
时候就发生crash,而此时fragment是存在的。
可以得出结论此时的fragment确实已经存在但是无法找到之前的容器vpProfitContainer(#7f0f03f8),那就是说明
在OnResume时actvity的视图重建还未好,但是viewpager的已经加载了,即时序出现了问题
详细原因描述:
这个异常通常发生在listview嵌套viewpager且视图重建时,此时在 【fragments在被添加到viewpager】这个过程发生在【viewpager被添加到父容器】之前。换而言之就是 getView()方法返回值前,fragments已经被inflated,此时在fragment中(见前面源码)已经调用
container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
来寻找viewapger容器,由于getView方法还未来得及返回此时viewPager处于detach状态的,从而根据f.mContainerId
找不到fragment的容器
并且做了如下尝试
从而验证了这个问题。
解决问题
1、目前采用是将这一块操作放到afterViewCreate中,从而避开了再走getView方法区获得子view(bindView)
2、在bindView中处理加上延时,确保getview已经处理完返回
Fragment 源码分析
- 什么是FragmentTransaction(BackStackRecord中实现)
它封装了一系列对fragment的操作,并一次性执行这些操作,如add、remove、replace、show、hide等
通过双向链表结构
`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;static final class Op { Op next; Op prev; int cmd; Fragment fragment; int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; ArrayList<Fragment> removed;}Op mHead;Op mTail;int mNumOp;int mEnterAnim;int mExitAnim;int mPopEnterAnim;int mPopExitAnim;int mTransition;int mTransitionStyle;boolean mAddToBackStack;boolean mAllowAddToBackStack = true;`
阅读源码发现Fragment中replace、add、remove、hide、show ————> addOp,如
` public FragmentTransaction show(Fragment fragment) { Op op = new Op(); op.cmd = OP_SHOW; op.fragment = fragment; addOp(op); return this;}`
addOp
` void addOp(Op op) { if (mHead == null) { mHead = mTail = op; } else { op.prev = mTail; mTail.next = op; mTail = op; } //记录转场动画的信息 op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; mNumOp++;}`
记录链表信息的双向链表操作,主要是操作不同cmd的Op对象,通过双链表维护起来,由于Fragment在activity层面维护了一个回退栈,在使用Fragment时候,如果不开启回退栈它是直接销毁再重建,但是如果将Fragment任务添加回退栈后,则不会销毁,按回退就会显示到栈顶,
`addToBackStack(null) popBackStack() getBackStackEntryCount()`
commit
BackStatckRecord#commitInternal(mManager.enqueueAction(this, allowStateLoss);)可以看到commit方法并没有立即执行之歌动作,而是入队了action,系统会在下次eventloop到来时来执行它,这里的this就是指代BackStackRecord#run
方法,因为BackStackRecord实现了Runnable接口,这个run方法主要就是向前遍历Op双向链表,根据cmd的不同调用FragmentManagerImpl的以下方法 ,add、repleace、remove等方法,可以看出replace相当于先执行remove再add
case OP_REPLACE: {
Fragment f = op.fragment;
if (mManager.mAdded != null) {
for (int i=0; i<mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
if (FragmentManagerImpl.DEBUG) Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
}
mManager.removeFragment(old, transition, transitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
}
} break;
` case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = exitAnim; mManager.hideFragment(f, transition, transitionStyle); } break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = enterAnim; mManager.showFragment(f, transition, transitionStyle); } break;`
对应该FragmentImpl中的实现函数
`public void hideFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "hide: " + fragment); if (!fragment.mHidden) { fragment.mHidden = true; if (fragment.mView != null) { Animation anim = loadAnimation(fragment, transition, false, transitionStyle); if (anim != null) { fragment.mView.startAnimation(anim); } fragment.mView.setVisibility(View.GONE); } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(true); }}public void showFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "show: " + fragment); if (fragment.mHidden) { fragment.mHidden = false; if (fragment.mView != null) { Animation anim = loadAnimation(fragment, transition, true, transitionStyle); if (anim != null) { fragment.mView.startAnimation(anim); } fragment.mView.setVisibility(View.VISIBLE); } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(false); }}`
可以看出show/hideFragment只是改变fragment根View的visibility,最多带上个动画效果,另外只有本身是hidden的fragment,调用show才起作用,否则没用的,fragment.onHiddenChanged会被触发;其次不会有生命周期callback触发
这些Op处理完之后,就调用了mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
开始进入FragmentManagerImpl中处理
什么是FragmentManager(FragmentManagerImpl中实现)
它是和某个activity相关联的,并不是全局唯一的,而是每个actvity都有自己的FragmentManager
,内部都有自己的状态mCurState,对应外部activity的生命周期状态,它提供和activity中fragment交互的API FragmentManager
是一个抽象类,里面有一个实现类FragmentManagerImpl
和一个状态管理类FragmentManagerState
- Fragment状态管理
FM里面维护一个自己的状态,当导入一个Fragment的时候,FM的目的就是为了让Fragment个自己的状态基本保持一致.它有自身的状态机,而它的状态可以理解为与Actvity本身同步
- 关键函数
FragmentManager#moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)
FragmentManager的每一次状态变更都会引起mActive里面的Fragment的状态变更,而mActivity
是所有纳入FM管理的Fragment容器,
关于状态变更参见Fragment
`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.int mState = INITIALIZING;`
可以看出状态越靠后值越大,在FM管理时也是直接通过状态数值比较来决定,如
`if (f.mState < newState) ... else { ... `
这些正好是和act的生命周期对应起来,也就是说这些方法是随着act进入到不同的生命周期而被调用的,即mCurState的值是被这些方法触发设置的。比如act进入到了Resume状态,那么FragmentManagerImpl.mCurState也就等于Fragment.RESUMED。主要是通过通过dispatchXXX函数调用对应的moveToState
- 关键函数分析
这个方法最终会将FragmentManager的状态赋值给fragment,另外这个方法会根据不同的state调用各种onAttach, Fragment.performXXX,进而调到用户自己override的fragment的各种生命周期方法,比如onCreate、onCreateView等等。主要注意的是 CREATED状态
`case Fragment.CREATED: if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + f.getResources().getResourceName(f.mContainerId) + ") for fragment " + f)); } }`
每次都是查找自己的container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
容器`
- ListView嵌套ViewPager+Fragment引起的Bug
- fragment嵌套viewpager viewpager嵌套fragment 的bug
- Fragment中ViewPager嵌套Fragment引起的问题
- Android_BUG_ViewPager+Fragment(Fragment中嵌套Viewpager,Viewpager中有嵌套Fragement的场景)爆出的BUG
- Fragment嵌套引起的错误
- Viewpager嵌套ScrollView和ListView嵌套ScrollView引起的冲突问题
- 关于viewpager里Fragment嵌套Fragment的一些列bug解决
- Viewpager + Fragment 的双层嵌套
- Viewpager 嵌套fragment的问题
- viewpager的fragment嵌套viewpager的问题
- 简单的Fragment切换,Fragment嵌套Viewpager
- viewPager+fragment嵌套viewPager+fragment
- ViewPager+Fragment嵌套ViewPager+Fragment
- ViewPager+Fragment引起的错误:No Activity
- ViewPager+Fragment引起的错误:No Activity
- 关于viewpager中嵌套的fragment中的listview的横向滑动的滑动冲突的问题。
- 关于viewpager+fragment中嵌套viewpager+fragment的问题处理:
- 关于viewPager+radioGroup+Fragment嵌套,其中一个有listview,数据空白的问题
- NoSQL类型介绍及适用场景
- 第一天
- ajax成功请求到后台,但是前端报404错误
- 5.C#:stringBuilde的特性和使用
- 2017.1.12--linux下的C语言--SOCKET编程
- ListView嵌套ViewPager+Fragment引起的Bug
- JS简单键盘按键秒表计时器3
- Android性能优化
- verilog代码中的"parameter" "#" "localparam"
- 3,4单元总结
- $.ajax请求数据时,请求文件内容不能用单引号
- android复习路之service综合篇
- Mysql数据库查询语句DQL(Data Query Language)—— 基本查询
- 矩阵的乘法和求逆(C语言实现)