RecyclerView OnCreateViewHolder性能问题及优化方案咨询
Let's tackle this RecyclerView performance issue step by step—your problem with slow layout inflation is super common when dealing with complex grid items, so let's break down the fixes and alternatives you can try:
1. Optimize the Item Layout First (Most Impactful)
The root cause here is almost certainly the complex hierarchy in your item layout. Even without data binding, inflating nested views and custom components eats up main-thread CPU. Let's trim this down:
- Flatten the layout hierarchy: Your current item uses a
FrameLayoutwrapping aCardView, butCardViewis already a subclass ofFrameLayout—you can remove the outerFrameLayoutentirely. This cuts one unnecessary layer right away. - Replace nested layouts with ConstraintLayout: Convert the content inside the
CardViewto useConstraintLayoutinstead of relying on implicitFrameLayoutpositioning. ConstraintLayout eliminates nested view groups, which drastically reduces inflation and measurement time. - Audit your CustomViews:
CustomView1,CustomView2, andCustomView3might be adding unnecessary overhead. Check if theironMeasure/onDrawmethods have heavy computations, or if they contain their own nested layouts that can be flattened. If they don't need to be custom views, replace them with standard layout components where possible. - Lazy-load included layouts: If
someLayoutToIncludeisn't visible on every item (or isn't needed immediately), replace the<include>tag with aViewStub. This delays inflation of that sub-layout until it's actually required. - Simplify CardView: If you can tolerate minor visual changes, reduce or remove
cardElevation(shadow rendering is expensive) or replace the CardView's rounded corners with a customShapeDrawablebackground—this avoids the extra layer CardView adds for elevation compatibility.
Here's a simplified version of your item layout after these changes:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" card_view:cardCornerRadius="3dp" card_view:cardElevation="2.5dp" card_view:cardUseCompatPadding="true"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <CustomView1 android:id="@+id/custom1" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <CustomView2 android:id="@+id/custom2" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/custom1" /> <!-- Add other views with proper constraints --> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_marginStart="2dp" android:layout_marginTop="2dp" android:layout_marginEnd="2dp" android:adjustViewBounds="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ViewStub android:id="@+id/stub_some_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout="@layout/someLayoutToInclude" app:layout_constraintTop_toBottomOf="@id/custom2" /> </androidx.constraintlayout.widget.ConstraintLayout> </android.support.v7.widget.CardView>
2. Improve ViewHolder Caching (Better Than Pre-Fetch Alone)
Your current attempts with setItemViewCacheSize and setInitialPrefetchItemCount are good, but they only target the view cache. To reduce onCreateViewHolder calls more effectively, leverage the RecycledViewPool:
- Increase the recycled view pool size: By default, the pool only holds a small number of views per view type. Boost this to match the number of items visible on screen plus a buffer:
recyclerView.getRecycledViewPool().setMaxRecycledViews(YOUR_VIEW_TYPE, 15); // Adjust based on your screen size - Pre-load ViewHolders in the background: Before the RecyclerView is displayed, create extra ViewHolders off the main thread and add them to the pool. This ensures the pool has ready-to-use views when the user scrolls:
Note: Using a dummy parent is safe here—RecyclerView will re-measure the view when it's attached to the actual layout.new Thread(() -> { Context context = getContext(); ViewGroup dummyParent = new FrameLayout(context); // Dummy parent for layout params for (int i = 0; i < 10; i++) { View view = LayoutInflater.from(context).inflate(R.layout.item_layout, dummyParent, false); ItemViewHolder holder = new ItemViewHolder(view); recyclerView.getRecycledViewPool().putRecycledView(holder); } }).start();
3. Using AsyncLayoutInflater (Yes, It's Possible)
You absolutely can use AsyncLayoutInflater to offload inflation to a background thread, but you need to handle it carefully since RecyclerView expects onCreateViewHolder to return a ViewHolder immediately. Here's a practical approach:
- Return a placeholder ViewHolder first: Create a minimal placeholder view (with the same size as your actual item) to return immediately, avoiding main-thread blocking.
- Inflate the real view in the background: Use
AsyncLayoutInflaterto inflate the complex layout off-thread, then replace the placeholder view in the ViewHolder once inflation is done.
Here's how to implement this in your Adapter:
private AsyncLayoutInflater asyncLayoutInflater; @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); asyncLayoutInflater = new AsyncLayoutInflater(recyclerView.getContext()); } @NonNull @Override public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // Create a placeholder view with matching layout params View placeholder = new View(parent.getContext()); placeholder.setLayoutParams(new RecyclerView.LayoutParams( RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT )); ItemViewHolder holder = new ItemViewHolder(placeholder); // Inflate real view in background asyncLayoutInflater.inflate(R.layout.item_layout, parent, false, (inflatedView, resid, parentView) -> { // Replace placeholder with real view holder.itemView = inflatedView; // Rebind data if the holder is still attached to a position int position = holder.getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { onBindViewHolder(holder, position); } }); return holder; }
To avoid layout jumps, make sure the placeholder view has the exact same height/width as your real item. You can hardcode the size or measure it once upfront.
4. Bonus: Other Quick Wins
- Avoid
wrap_contentwhere possible: If your item has a fixed height, set it explicitly instead of usingwrap_content—this reduces measurement time during inflation. - Enable hardware acceleration: Ensure all your custom views have hardware acceleration enabled (most do by default, but check if any disable it in code).
- Profile again after changes: Use Android Studio's Profiler to verify which changes are making the biggest impact—focus on the ones that reduce main-thread time during inflation and scrolling.
内容的提问来源于stack exchange,提问作者user4618429

