1. Introduction

I believe that most Android developers to RecyclerView cache mechanism. There are countless tutorials. If you want to learn more about what these different caches do and how they work. You can refer to my two previous posts. Talk about RecyclerView cache mechanism and detailed talk about RecyclerView cache mechanism, the former mainly introduces the role of various levels of cache and the difference between them, the latter mainly explains how to achieve cache from the perspective of source code. The cache architecture diagram is as follows:

Today we’re going to focus on the ViewCacheExtension cache
public abstract static class ViewCacheExtension {

    public abstract View getViewForPositionAndType(
        Recycler recycler,
        int position,
        int type
    );
}
Copy the code

ViewCacheExtension is an interface reserved by the RecyclerView framework for developers to implement their own cache logic. Strangely enough, no matter how much you search, it’s hard to find the right way to use ViewCacheExtension. The tutorials on the web are fairly consistent in their characterization, but since ViewCacheExtension only provides a getView and does not provide a putView method, it is of little use. This is incorrect, of course, and this article is a remake of ViewCacheExtension. When we exhausted all methods, the RecyclerView tuning scheme is exhausted, using ViewCacheExtension became the last kilometer of RecyclerView performance optimization to the extreme.

There was a time when I was too young to say ViewCacheExtension was useless. The following figure refers to the RecyclerView cache mechanism written by myself

2. What can ViewCacheExtension do for performance optimization?

“Reducing the nesting level of an ItemView to make the layout as lightweight as possible” or reducing the inflate length of an ItemView are two of many Tips for RecyclerView performance optimization. Such a scheme is certainly fine. But the reality may be that the ItemView itself is very complex, and the inflate takes time after optimizing its layout or the ItemView was written by a predecessor, which is so complex that subsequent developers are unable or unwilling to modify it. In this case how to further optimize to the utmost. Explain, of course, what I have done with ConstraintLayout. I am competent and hardworking, and I have confidence in my ability to interpret a complex and inefficient layout written by my predecessors. Well, to say the least, you’ve done pretty well. The ItemView inflate takes time and blocks the thread when the RecyclerView is initialized. Let’s say I have 10 ItemViews, each of which takes 20ms, and that blocks the main thread for 200ms. Is there any way to optimize it?

The answer, of course, is yes. Optimize with ViewCacheExtension. Use it to optimize the length of blocking the main thread when creating RecyclerView initialization.

3. Start with a case study

First, simulate the scene of a complex View. The constructor of TextView sleeps for 100ms.

class HeavyTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
    init {
        println("heavy view init")
        Thread.sleep(100L)
    }
}
Copy the code

RecyclerView interface is very simple, just a few TextViews. ItemView layout file code is as follows:

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginTop="5dp" android:layout_marginRight="5dp" android:layout_marginBottom="5dp"> <com.peter.viewgrouptutorial.recyclerview.HeavyTextView android:id="@+id/heavy.text" android:layout_width="match_parent"  android:layout_height="wrap_content" android:background="@drawable/white_touch" android:clickable="true" android:orientation="horizontal" android:padding="@dimen/small" android:textSize="14sp" /> </androidx.cardview.widget.CardView>Copy the code

The running results of the program are as follows:

We look at RecyclerView performance through Systrace

We can see from the picture above. The total cost of initializing HeavyTextView was 639ms. We know that Android will drop frames if it takes more than 16ms per frame. So it’s relatively slow. When you actually run the program, you’ll also find that jumping to the Activity is obviously not smooth.

Compare the optimized results.The premise is that HeavyTextView remains dormant for 100ms without modifying it

Compared to the RV OnLayout event, the optimized effect takes only 76ms. Nearly 10 times more room for optimization. The actual effect is that the jump Activity is very smooth and smooth.

4. Optimize the plan

The UI model of the program is shown as follows, jumping from AActivity to BActivity, it has a RecyclerView list.

The AActivity code is as follows:

Image version code:

Kotlin version code is easy to copy

Class AActivity: AppCompatActivity() {companion object {static variable, ArrayList can save developers cache View var sCustomViewCaches: ArrayList<View> = arrayListOf() } override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) // When the AActivity MessageQueue is free, Create 10 HeavyText layouts ItemView looper.myQueue ().addidleHandler {thread {repeat(10) {val linearLayout = LinearLayout(this@AActivity). Apply {orientation = LinearLayout.VERTICAL} // Add the itemView to the LinearLayout and remove it. Inflate (r.layout.custom_cache_view_item) To display the padding correctly in the item layout val itemView = layoutInflater.from (this@AActivity).inflate(r.layout.custom_cache_view_item, LinearLayout) linearLayout. RemoveView (itemView) / / background is set to RED in order to correctly in the cache are used to test whether the View itemView. SetBackgroundColor (Color. RED) itemView.layoutParams = RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup. LayoutParams. WRAP_CONTENT) / / reflection setting RecyclerView. LayoutParams mViewHolder attribute val viewHolderField = RecyclerView.LayoutParams::class.java.getDeclaredField("mViewHolder") .apply { isAccessible = true } Void createViewholder val ViewHolder = object; Recyclerviewholder (itemView) {// Set mItemViewType of ViewHolder to 0. Specific business concrete realization. Mainly in order to reuse the with (RecyclerView. ViewHolder: : class. Java. GetDeclaredField (" mItemViewType "). The apply {isAccessible = true}) { set(viewHolder, 0) } viewHolderField.set(itemView.layoutParams, SCustomViewCaches. Add (ItemView)} println(" CustomView cache OK ")} false}}Copy the code

The BActivity implementation is as follows

Image version code:

Kotlin version code is easy to copy

class BActivity : AppCompatActivity() { private lateinit var mRecyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_recycler_view_custom_cache) mRecyclerView = FindViewById (r.id.clerView) // Omit a lot of RecyclerView general operations such as setAdapter and LayoutManager mRecyclerView.setViewCacheExtension(object : RecyclerView.ViewCacheExtension() { override fun getViewForPositionAndType( recycler: RecyclerView.Recycler, position: Int, type: Int ): View? {/ / take the View from the cache of AActivity, Demo instance, actual business can write more elegant if (AActivity. SCustomViewCaches. The size! = 0) { val view = DashboardActivity.sCustomViewCaches.removeFirst() println("custom cache view remove $position $view") if (position == 0) { println("attention $position $view") } return view } return null } }) } }Copy the code

5. Encountered pits

  1. Null pointer exception. Solution: set RecyclerView for itemView. LayoutParems.

  1. ViewHolder cannot be null. Solution: Reflection set ViewHolder.

  1. The layout spacing is incorrect. Solution: Now add itemView to temporary viewGroup and remove it.

  2. Incorrect cache reuse. Reflection sets itemViewType of ViewHolder.

  3. The cache is not enough. RecyclerView layout_height=”wrap_content”, “match_parent” It has to do with the measurement mechanism.

Follow the wechat official account of bytecode. Get the latest articles.