背景#
今週、商業化の要件(広告)を書く際に、再利用を簡単にするために渡す Bean を少し変更しました。最初の実行時はすべて正常でしたが、2 回目以降の実行時に空ポインタ例外が表示され、例外のスタックは 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()
を見つけました:
// 重要な2行のコードのみを保持
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;