探究jumpDrawablesToCurrentState问题发生原因

你永远都追不上比你优秀的人,因为他们比你更努力~ 【今日份丧】

最近日常的需求量激增,写代码写的石乐志。上周在实现一个ViewPager+Fragment的时候在Fragment里面的onCreateView写下了下面这行代码

  override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
      savedInstanceState: Bundle?): View? {
    mRootView = inflater?.inflate(R.layout.fragment_doemstic_city, container)
    initView()
    initIndexBar()
    initData()
    return mRootView
  }

正常看也没觉得有毛病,但是一旦run起来,你会看到如下报错(移除部分信息):

java.lang.StackOverflowError: stack size 8MB
  at android.view.ViewGroup.jumpDrawablesToCurrentState(ViewGroup.java:6958)
  at android.view.View.onAttachedToWindow(View.java:16862)
  at android.view.ViewGroup.onAttachedToWindow(ViewGroup.java:4837)
  at android.support.v4.view.ViewPager.onAttachedToWindow(ViewPager.java:1538)
  at android.view.View.dispatchAttachedToWindow(View.java:17377)
  at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3319)
  at android.view.ViewGroup.addViewInner(ViewGroup.java:4955)
  at android.view.ViewGroup.addViewInLayout(ViewGroup.java:4891)
  at android.view.ViewGroup.addViewInLayout(ViewGroup.java:4869)
  at android.support.v4.view.ViewPager.addView(ViewPager.java:1477)
  at android.view.ViewGroup.addView(ViewGroup.java:4686)
  at android.view.ViewGroup.addView(ViewGroup.java:4659)
  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1427)
  at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1752)
  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1821)
  at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:797)
  at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2595)
  at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2382)
  at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2337)
  at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2214)
  at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:649)
  at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:145)
  at android.support.v4.view.ViewPager.populate(ViewPager.java:1238)
  at android.support.v4.view.ViewPager.populate(ViewPager.java:1086)
  at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1616)
  at android.view.View.measure(View.java:21998)
  
黑人问号

怎么办??? 谷歌一下

网上给出的说法和解法

Inflate里面把ViewGroup传进去了,因为每一个View只能有一个父view即parentView。当container不为空时,比如此fragment所待在的activity的layout。而onCreateView中返回的view是给ViewPager使用的,所以就会出现这个view有两个parentView-即activity的layout和viewPager,所以会报出异常。解法如下:

//错误示范
mRootView = inflater?.inflate(R.layout.fragment_doemstic_city, container)
//正确写法
 mRootView = inflater?.inflate(R.layout.fragment_doemstic_city, null)
 mRootView = inflater?.inflate(R.layout.fragment_doemstic_city, container,false)

根据inflate的源码我们知道当传入的container不为null时,container会成为R.layout.XX布局的RootView
但是难道不应该报错是The specified child already has a parent. You must call removeView() on the child’s parent first......为什么会是StackOverflowError

不生气 不生气 Let's read the fucking source code

首先根据错误信息可以知道这次事件发生的简单经过是:
ViewPager(onMeasure -> populate -> addView)
ViewGroup(addViewInLayout -> addViewInner)
View(dispatchAttachedToWindow -> onAttachedToWindow -> jumpDrawablesToCurrentState)

日常经验可知在onMeasure和populate时是得不到有用的信息,我们从addView的时候开始追吧!!

ViewPager:addView

 @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
        final LayoutParams lp = (LayoutParams) params;
        // Any views added via inflation should be classed as part of the decor
        lp.isDecor |= isDecorView(child);
        if (mInLayout) {
            if (lp != null && lp.isDecor) {
                throw new IllegalStateException("Cannot add pager decor view during layout");
            }
            lp.needsMeasure = true;
            addViewInLayout(child, index, params);
        } else {
            super.addView(child, index, params);
        }

        if (USE_CACHE) {
            if (child.getVisibility() != GONE) {
                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
            } else {
                child.setDrawingCacheEnabled(false);
            }
        }
    }

没有有用的信息,接着看addViewInLayout

ViewPager:addViewInLayout

   protected boolean addViewInLayout(View child, int index, LayoutParams params) {
        return addViewInLayout(child, index, params, false);
    }

   protected boolean addViewInLayout(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        //注意看这里,强行将child的parent置空,难怪没有出现The specified child already has a parent. You must call removeView() on the child’s parent first
        child.mParent = null;
        addViewInner(child, index, params, preventRequestLayout);
        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        return true;
    }

有了一点点眉目

ViewGroup:addViewInner

    if (mTransition != null) {
        // Don't prevent other add transitions from completing, but cancel remove
        // transitions to let them complete the process before we add to the container
        mTransition.cancel(LayoutTransition.DISAPPEARING);
    }

    //在ViewPager里面child.parent = null 所以这里才没有出发这个异常 为后面的StackOverflowError埋下了伏笔
    if (child.getParent() != null) {
        throw new IllegalStateException("The specified child already has a parent. " +
                "You must call removeView() on the child's parent first.");
    }

    //省略无用信息
    AttachInfo ai = mAttachInfo;
    if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
        boolean lastKeepOn = ai.mKeepScreenOn;
        ai.mKeepScreenOn = false;
        child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
        if (ai.mKeepScreenOn) {
            needGlobalAttributesUpdate(true);
        }
        ai.mKeepScreenOn = lastKeepOn;
    }

   //省略无用信息

}

好了到这里原因已经很明显了,因为ViewPager里面强行将childView的parentView置空,导致在ViewGroup里面没有检测出来,而实际情况确是因为错误的写法导致childView以及attach到了container上,而之后又被add到了ViewPager上,这种情况导致了整个布局树中存在循环引用,所以在最后会无限制的调用jumpDrawablesToCurrentState,递归无法结束....

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容