记RecyclerView.shouldIgnore() NPE

背景

这周在写商业化需求 (广告) 时，为了方便复用，简单改了下传入的 Bean。第一次运行时没有一切正常，第二次之后再运行时，提示空指针异常，异常的堆栈是 RecyclerView，异常信息 (当时没有保存日志，找了其他人相同异常的堆栈，原因是同一个)：

Copy
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():

Copy
// 只保留了关键的两行代码
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 用一个新建的对象覆盖了，类似这样：

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

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

解决

原因就是替换了原来的 LayoutParams，而新的 LayoutParams 中 mViewHolder 又没有赋值。用 mAdBinding.getRoot ().getLayoutParams () 再改属性就可以了。像这样：

Copy
ViewGroup.LayoutParams layoutParams = mBinding.getRoot().getLayoutParams();
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;