多模块项目中使用ViewBinding填充RecyclerView项视图遇空指针异常
问题背景
我有一个多模块Android应用,ui模块中包含RecyclerView要展示的项布局,checkout模块的CheckoutFragment包含该RecyclerView。使用ViewBinding加载布局时出现NullPointerException,但传统findViewById方式加载正常,调试发现add_item、remove_item和quantity_selector对应的视图为null,其余视图可正常初始化。
CheckoutFragment代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.checkoutCartList.layoutManager = LinearLayoutManager(requireContext()) binding.checkoutCartList.adapter = CheckoutAdapter(arrayOf(CartItem("xxx", 1, "9,99 €"))) }
CheckoutAdapter代码
class CheckoutAdapter(private val list: Array<CartItem>) : RecyclerView.Adapter<CheckoutAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = ItemCheckoutCartProductBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(binding) } override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { viewHolder.bind(list[position]) } override fun getItemCount() = list.size class ViewHolder(private val binding: ItemCheckoutCartProductBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: CartItem) { } } }
异常信息
java.lang.NullPointerException: Missing required view with ID: it.xxxx.app.dev:id/add_item at it.xxxx.ui.databinding.ItemCheckoutCartProductBinding.bind(ItemCheckoutCartProductBinding.java:155) at it.xxxx.ui.databinding.ItemCheckoutCartProductBinding.inflate(ItemCheckoutCartProductBinding.java:87)
ItemCheckoutCartProduct布局代码
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/view_32dp_space"> <com.google.android.material.card.MaterialCardView android:id="@+id/card_image_left" android:layout_width="@dimen/view_128dp_space" android:layout_height="@dimen/view_128dp_space" app:cardCornerRadius="@dimen/view_16dp_space" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:strokeColor="@color/colorGrey" app:strokeWidth="1dp"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/product_image" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/view_16dp_space" android:src="@drawable/ic_thumb_up_red" /> </com.google.android.material.card.MaterialCardView> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/product_description" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/view_24dp_space" android:fontFamily="@font/font_family_bold" android:textColor="@android:color/black" android:textSize="@dimen/text_16sp_size" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toEndOf="@id/card_image_left" app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.card.MaterialCardView android:id="@+id/quantity_selector" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/view_24dp_space" android:layout_marginTop="@dimen/view_8dp_space" app:cardCornerRadius="@dimen/view_24dp_space" app:layout_constraintStart_toEndOf="@id/card_image_left" app:layout_constraintTop_toBottomOf="@id/product_description" app:strokeColor="@color/colorGrey" app:strokeWidth="1dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/view_8dp_space" android:orientation="horizontal" android:weightSum="3"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/remove_item" android:layout_width="@dimen/view_12dp_space" android:layout_height="@dimen/view_12dp_space" android:layout_gravity="center" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/ic_minus" android:tint="@color/colorGrey" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/product_quantity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginHorizontal="@dimen/view_16dp_space" android:layout_weight="1" android:fontFamily="@font/font_family_bold" android:textColor="@android:color/black" android:textSize="@dimen/text_14sp_size" tools:text="1" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/add_item" android:layout_width="@dimen/view_12dp_space" android:layout_height="@dimen/view_12dp_space" android:layout_gravity="center" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/ic_add" android:tint="@color/colorGrey" /> </LinearLayout> </com.google.android.material.card.MaterialCardView> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/delete_product_from_cart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/view_24dp_space" android:layout_marginTop="@dimen/view_24dp_space" android:fontFamily="@font/font_family_regular" android:text="@string/common_delete" android:textSize="@dimen/text_12sp_size" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/card_image_left" app:layout_constraintTop_toBottomOf="@id/quantity_selector" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/total_product_price_amount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@font/font_family_bold" android:textColor="@android:color/black" android:textSize="@dimen/text_16sp_size" app:layout_constraintBottom_toBottomOf="@id/delete_product_from_cart" app:layout_constraintEnd_toEndOf="parent" tools:text="4,20$" /> </androidx.constraintlayout.widget.ConstraintLayout>
问题原因及解决方案
核心原因
这是多模块环境下资源ID冲突或ViewBinding绑定类引用错误模块资源导致的:
- 若
checkout模块和ui模块存在同名的资源ID(哪怕在不同布局中),Gradle合并资源时会覆盖ui模块的资源,导致ViewBinding查找不到正确的视图。 - ViewBinding生成的绑定类可能混淆了模块的资源命名空间,导致加载布局时引用了错误模块的资源定义。
解决步骤
排查并修改重复的资源ID
搜索add_item、remove_item、quantity_selector这几个ID在所有模块的布局文件中是否存在重复定义,若有则修改ID名称(比如加上模块前缀,如ui_add_item、checkout_add_item)。清理缓存并重新构建项目
执行File -> Invalidate Caches... -> Invalidate and Restart,或在终端运行./gradlew clean build,解决ViewBinding生成的绑定类缓存问题。明确指定LayoutInflater的资源命名空间
在CheckoutAdapter的onCreateViewHolder中,确保加载的是ui模块的布局资源:override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val packageName = parent.context.resources.getResourcePackageName(R.layout.item_checkout_cart_product) val inflater = LayoutInflater.from(parent.context).cloneInContext( ContextThemeWrapper(parent.context, packageName) ) val binding = ItemCheckoutCartProductBinding.inflate(inflater, parent, false) return ViewHolder(binding) }确认模块依赖配置正确
检查checkout模块的build.gradle,确保对ui模块的依赖为implementation,保证资源可正常访问:dependencies { implementation project(":ui") }
内容的提问来源于stack exchange,提问作者giordy16

