● 需求:滑动变化页面,tab栏随之变化(很一般的常见需求了)
● bug现象描述:(1)Fragment没有被创建和添加进viewPager,(2)有时候activity加载出来时crash,(3)有时候contain的Activity正常加载,试图滑动页面就crash
● Log信息:
11-23 18:50:53.667 15943-15943/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.lanhi.dailynews, PID: 15943
java.lang.IllegalStateException: FragmentManager is already executing transactions
at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:1631)
at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:679)
at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:143)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1272)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1120)
at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1646)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:703)
at android.support.design.widget.HeaderScrollingViewBehavior.onMeasureChild(HeaderScrollingViewBehavior.java:90)
at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onMeasureChild(AppBarLayout.java:1367)
at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:768)
at android.view.View.measure(View.java:16529)
at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1081)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:139)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
at android.view.View.measure(View.java:16529)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2291)
at android.view.View.measure(View.java:16529)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1973)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1170)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1352)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1057)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5666)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5072)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os
● 变量X:
(1)这次基类是AppCompatActivity,但它也是extends FragmentActivity的。
(2)layout的根view是CoordinatorLayout
(3)这次是动态创建Fragment,根据api请求回来中list.size()去new Fragment
(4)在new Fragment这个类文件时,使用了AS里的File - new Fragment,导致Fragment里初始化了一些系统默认的构造方法
(5)使用了Retrofit & RxJava来进行Http请求和返回信息的处理
● 猜测:
(1)变量X1问题,但可能性应该很小,因为它extends FragmentActivity,跟activity直接extends FragmentActivity应该没有区别
(2)变量X2问题,CoordinatorLayout与ViewPager的冲突,但以前写过的demo里这俩有过交集没有出现问题,可能性不大
(3)变量X3问题,debug时发现Frament是被init了的且添加进了List<Fragment>
● 行动:
(1)首先google了 java.lang.IllegalStateException:FragmentManager is already executing transactions,看了好多别人贴出的crash信息,虽然关键字差不多,但无法跟我的有太大联系(这种看似相同Exception,但对别人有用的solution用来调整自己的代码并不奏效的情况时常发生,我也有了经验就是:很可能根本原因不是这个,而根本原因可能是一个非常细枝末节看似无足轻重甚至发现时感叹自己太疏忽的原因)
(2)google无果,那就设断点debug吧。最后在
viewPager.setAdapter(adapter);
F6时竟然跳入了SafeSubscriber#onNext()方法
@Override
public void onNext(T args) {
try {
if (!done) {
actual.onNext(args);
}
} catch (Throwable e) {
// we handle here instead of another method so we don't add stacks to the frame
// which can prevent it from being able to handle StackOverflow
Exceptions.throwOrReport(e, this);
}
}
捕获了Exception:
OnFragmentInteractionListener???这个Listener怎么这么眼熟??突然醒悟过来,这是new的Fragment中的一个回调方法啊,在MainActivity中实例化了Fragment,但是没有实现F中的接口,为什么呢,因为这个接口是在我File - new - Fragment时,AS自己创建了Fragment类模板代码里的Listener方法,而这个模板方法也确实说了
我虽然以前也用过这种方法生成默认的Frament类,但那会儿还不太熟悉AS自动创建的代码,就挨个看了每个方法都是什么,这回反而因为熟悉却遗漏了根本的东西。
在MainActivity里implements 了这个OnFragmentInteractionListener,一切正常了......
● 反思:为什么这么低级的错误我花了2个小时?
(1)过度依赖google,在出现问题时没有去独立思考这个问题并独立解决(debug),在搜索无果并隐约感觉到这回的情况可能又是因为在上面【行动(1)红字】中的情况,才去debug,这让我想起了看《代码大全》里让我印象很深的一段话,大概意思是说「循环条件中当你不确定是i<length还是i<length+1或者不确定该用<=还是<,你该去仔细观察到底代码的每一步运行发生了什么,而不是猜测式地侥幸编程,现象正常了就ok了,你必须知道你写的代码里到底发生了什么」。“你必须知道代码里发生了什么”,这很关键,如果以此为核心,出现问题了就应该首先debug,观察每一步发生了什么,而不是想着依赖着别人的solution去google。
(2)在确定变量X时(也就是以前解决类似需求时和这次之间的变量),没有把所有X都考虑进去,变量X4、X5就是,开始确定变量时没有考虑这两点。这也是为什么在上面写的时候标成了蓝色,这两个原因是导致问题的关键变量。X4添加了不在意的Listener,使用了X5的RxJava,我在onSubscribe(....)方法中进行了数据操作,这个X4产生的Exception被X5中的一个类截获了,但是这个Excetion没有被在控制台打印出来,反而打印的是一开始贴出来的java.lang.IllegalStateException: FragmentManager is already executing transactions,我却将重点放在了去google这个Exception。可见没有被及时总结的变量很关键,而未知变量却很难在一开始被全部发现,往往都是事后才发现。这里的X4、X5就是后来想起的,当时归纳变量时也觉得我使用了Retrofit&RxJava能跟这个bug有什么关系?突然想起来福尔摩斯里的一句话“当你排除了所有的不可能,无论剩下的是什么,即使再不可能也一定是真相。”
(3)java.lang.IllegalStateException: FragmentManager is already executing transactions这个Exception到底怎么出来的?我现在还不清楚,待探究。
写在最后:
最近决定以后把项目中的crash log都记载到blog中,一是为了以后类似的问题有经验可循,另外更重要的就是反思自己解决问题的过程,找出思维中的漏洞并在以后避免。