gotcha

gotcha

記錄RecyclerView.shouldIgnore() NPE

背景#

這週在寫商業化需求 (廣告) 時,為了方便復用,簡單改了下傳入的 Bean。第一次運行時沒有一切正常,第二次之後再運行時,提示空指針異常,異常的堆棧是 RecyclerView,異常信息 (當時沒有保存日誌,找了其他人相同異常的堆棧,原因是同一個):

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean androidx.recyclerview.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:3951)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3652)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.appcompat.widget.LinearLayoutCompat.setChildFrame(LinearLayoutCompat.java:1645)
at androidx.appcompat.widget.LinearLayoutCompat.layoutVertical(LinearLayoutCompat.java:1499)
at androidx.appcompat.widget.LinearLayoutCompat.onLayout(LinearLayoutCompat.java:1407)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.layoutDecoratedWithMargins(RecyclerView.java:9322)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1615)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3641)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:758)
at android.view.View.layout(View.java:19590)
at android.view.ViewGroup.layout(ViewGroup.java:6053)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2484)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2200)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1386)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6733)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:723)
2018-10-20 19:24:30.408 20730-20730/com.wynk.basic E/AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:658)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

下意識以為又是 Android Studio 抽風 (公司電腦經常的事),清除快取 + 重啟 + 重裝一套組合拳下來問題還是沒有解決。終於意識到就是代碼有問題。
改動影響的範圍很小,很容易鎖定。異常對戰中出現 RecyclerView,而且之前沒有出現過,懷疑是寫法有問題。看寫法和之前又沒有什麼差別,開始迷茫懷疑人生....

找問題#

看不出來,找到了拋出異常的代碼ViewHolder.shouldIgnore():

// 只保留了關鍵的兩行代碼
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i);
    if (holder.shouldIgnore()) {
        continue;
    }
    
// getChildViewHolderInt
    static ViewHolder getChildViewHolderInt(View child) {
        if (child == null) {
            return null;
        }
        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
    }

其實到這問題已經差不多了,child 為 null 或者 LayoutParams 中的 mViewHolder 為 null 才會有這種情況,代碼中又沒有移除子 View 的操作,只可能 mViewHolder 為 null,但... 沒看出來。
等到把異常信息RecyclerView$ViewHolder.shouldIgnore() NullPointerException甩到 Google 上找到了,又看了這
回項目裡邊找代碼,果然,代碼裡為了重新計算 View 的寬高,把 ViewHolder#mItemView 的 LayoutParams 用一個新建的對象覆蓋了,類似這樣:

mBinding.getRoot().setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

LayoutParams 裡的 mViewHolder 肯定也就是 null 了。

解決#

原因就是替換了原來的 LayoutParams,而新的 LayoutParams 中 mViewHolder 又沒有賦值。用 mAdBinding.getRoot ().getLayoutParams () 再改屬性就可以了。像這樣:

ViewGroup.LayoutParams layoutParams = mBinding.getRoot().getLayoutParams();
                    layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                    layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。