探究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,遞歸無法結束....

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容