Use optimization | points that can be optimized in RecyclerView

Posted by Franko126 on Tue, 15 Feb 2022 02:56:30 +0100

RecyclerView core knowledge points

1. What is RecyclerView

  • Display a large amount of data and flexible View for a limited screen, as shown in the following figure
  • Compare ListView

ListView:

  • There is only a vertical list layout
  • There is no API that supports animation
  • The interface design is inconsistent with the system, such as setOnItemClickListener
  • ViewHolder is not enforced
  • Performance is not as good as RecyclerView

RecyclerView:

  • Linear, Grid and stacked Grid layouts are supported by default
  • Friendly ItemAnimator animation api. When refreshing, call the corresponding refresh api to see the animation
  • Enforce ViewHolder
  • The source code of RecyclerView is very decoupled, and its performance is very good

2. Important components in RecyclerView

  • RecyclerView: a special ViewGroup that doesn't do much work itself. The important work will be completed by the following three components
    • LayoutManager: responsible for the layout and placement of item s
    • ItemAnimator: responsible for animation
    • Adapter: adapter mode, which adapts the data and converts the data list into the ItemViewAdapter required by RecyclerView

3. Simple use

Demo

4. What is viewholder

​ ViewHolder and item are in one-to-one correspondence. When an item is created, a ViewHolder will be created, so that the ViewHolder can be directly obtained when the item is reused, so as to prevent repeated findViewById.

​ So even if you don't use ViewHolder, your item will still be reused. The difference is that it will redo the operation of findViewById.

​ Practice of ViewHolder: generally, we bind data in the onBindViewHolder method, but if there are multiple entries, this writing method will be very cumbersome. In this case, we can write the code of binding data in the ViewHolder.

5. The caching mechanism of RecyclerView

What is cached in RecyclerView is actually ViewHolder. ViewHolder and item are actually bound, so caching ViewHolder is equivalent to caching item.

  • 1,Scrap

The itemView inside the screen can be used directly

  • 2,Cache

The slid out View will be placed in the Cache. When the user slides backwards, it will directly obtain the viewholder from the Cache to avoid repeated creation of viewholder. The Cache obtained from the Cache can be used directly without re creating bindable data.

  • 3,ViewCacheExtension

The user-defined Cache policy, if defined by the user, will find the Cache in it. If not, directly go to the following Cache pool.

  • 4,RecyclerViewPool

The cache pool holds abandoned itemviews. If it is not found from the above cache, it will be found from RecyclerViewPoll

The data saved in RecyclerViewPoll is dirty. Even if it is found in RecyclerViewPoll, the ViewHolder will not be re created, but the onBindView operation will be re executed. This is also the difference between Poll and previous 1 and 2.

If none of the above level 4 caches exist, the ViewHolder is recreated. The final call is onCreateViewHolder, which is created by the user.

6. Statistics of item advertisements in RecyclerView

  • There is no problem with statistics through the getView() method in ListView. The getView() method is called every time you slide.
  • Through onBindViewHolder() in RecyclerView, you can make statistics? Possible error!

The onbindviewhold method is not called every time. You may have seen the item 10 times, but only counted it 5 or 6 times. In this case, the data is wrong.

How to solve it?

Statistics can be made through onViewAttachedToWindow(). Every time you execute this method, you will see it

7. You may not know the RecyclerView performance optimization strategy

  • Do not create a click event in the onBindViewHolder method

Create a click event when creating a ViewHolder, such as creating a click event in new ViewHolder() or in the initialization method of ViewHolder.

  • LinearLayoutManager.setInitialPrefetchltemCount() method

If the RecyclerView is nested with horizontal RecyclerView, when the user slides, the page may get stuck due to the need to create more complex RecyclerView and multiple sub views

Due to the existence of RenderThread, recyclerview will prefetch(RenderThread is a thread specially used for UI rendering, and many operations originally placed on the main thread are placed on this thread). In this way, the main thread will have more idle time during rendering. In this idle state, recyclerview can be used to prefetch

Setinitialprefetchltemcount (the number of item s visible when the horizontal list is first displayed). After calling this method, the user will not get stuck when sliding due to prefetch.

Note:

  • Only LinearLayoutManager has this API
  • Only the RecyclerView nested inside will take effect
  • RecyclerView.setHasFixedSize()
//Pseudo code
void onContentsChanged(){
	if(mHasFixedSize){
		layoutChildren();
	}else{
		requestLayout();
	}
}

From the pseudo code above, you can see that if there is a fixed size, you can directly layoutChildren, otherwise requestLayout().

requestLayout() will let RecyclerView go through the drawing process again.

Therefore, if the data of recycleView is fixed, you can set this method to true.

  • RecycledViewPoll shared by multiple recyclerviews

Note that this RecycledViewPool is not a RecyclerViewPool in the level 4 cache

RecyclerView will create a RecycledViewPool for itself by default

Usage scenario: if it is a tab page and there are many sub pages with roughly the same item s, you can set a shared RecycledViewPoll, which can improve certain performance. You can set it through the following code

RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
recycler1.setRecyclerViewPool(pool);
recycler2.setRecyclerViewPool(pool);
recycler3.setRecyclerViewPool(pool);
  • DiffUtil

Calculate the difference between two different lists, output a section of operation according to the calculated difference, and change the first list into the second list

  • Local update method: notifyItemXXX() is not applicable to all cases

It is possible that you are not sure which item you want to update, so you can only refresh it through notifyDataSetChange(), which will cause the whole layout to be redrawn, all viewholders to be rebound, and possible animation effects will be lost

  • DiffUtil is applicable to the situation that the whole page needs to be refreshed, but some data may be the same.

DiffUtili.Callback, which is used to calculate diff for the system

/**
 *A callback class used by DiffUtil when calculating the difference between two lists
 */
public abstract static class Callback {
    /**
     * Size of old data
     */
    public abstract int getOldListSize();

    /**
     * Size of new data
     */
    public abstract int getNewListSize();

    /**
     * Called by DiffUtil to determine whether two objects represent the same item
     * <p>
     * For example, if entries have unique IDs, the method should check that their IDs are equal
     *
     * @param oldItemPosition Location of old data in the list
     * @param newItemPosition Location of new data in the list
     * @return True if two items represent the same object; False if the two items are different
     */
    public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

    /**
     * When you need to check whether two items have the same data, it is called by DiffUtil. DiffUtil uses this information to detect whether the contents of an item have changed
     * <p>
     * areItemsTheSame This method will be called only when it returns true. For example, the IDs of two users are the same, but their data may have changed, so this method will be called.
     * <p>
     * @param oldItemPosition  Location of old data in the list
     * @param newItemPosition Location of new data in the list                   
     * return true Indicates that the data of the two lists are the same, and false indicates that the data has changed
     */
    public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

    /**
     * Called when areItemsTheSame returns true and areContentsTheSame returns false
     * <p>
     *	Update data. If this method is not implemented, there will never be incremental updates inside item
     * <p>
     * Default implementation returns {@code null}.
     *
     * @param oldItemPosition Location of old data in the list
     * @param newItemPosition Location of new data in the list           
     *
     * @return A valid object that represents a change between two items.
     */
    @Nullable
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        Bundle b = new Bundle()
        if(.....){
            b.put("key",value)
        }
        return b;
    }
}

So how to use it?

After testing, it is found that the applicable scenarios are as follows:

When refreshing the list, the general operation is to empty the original data, then fill in the new data, and finally not

But after using Diff, when you refresh the list, you only need to fill in the new data, then call the Diff method. Internally, the difference will be calculated through the algorithm, and then the new data will be retained. Retention here refers to adding, deleting, modifying and querying on the basis of the original data, so that the final result is the same as the refreshed data. Take a look at the case, as follows:

-Default refresh

-After using Diff

As can be seen from the above figure, obvious animation traces can be seen after using Diff. After using Diff, items that are the same as the original data in the new data will be retained, and all items that are different will be remove d (here refers to the data in the old data list), and finally the data in the new data will be added. In this way, the performance consumption and list jamming caused by calling notifyDataSetChanged() directly can be avoided.

  • Even, you can see that those data are duplicate:

Let's take a look at the specific implementation process

  • Use diff
class RvDiffItemCallback(val old: List<String>, val new: List<String>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return old.size
    }

    override fun getNewListSize(): Int {
        return new.size
    }
	//Judge whether the id is the same. Since it is a string and has no id, it is directly compared
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {

        return old[oldItemPosition] == new[newItemPosition]
    }

	//Judge whether the content is the same
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return old[oldItemPosition] == new[newItemPosition]
    }
}
class Adapter() : RecyclerView.Adapter<Adapter.Holder>() {
        var data = mutableListOf<String>()
      
        fun addNewData(list: MutableList<String>) {
            val diffResult = DiffUtil.calculateDiff(RvDiffItemCallback(data, list), false)
            data = list
                //If you don't need to see the animation, you can directly pass it in to this, otherwise you can implement it yourself
//            diffResult.dispatchUpdatesTo(this)
            diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
                override fun onChanged(position: Int, count: Int, payload: Any?) {
                    notifyItemRangeChanged(position, count, payload)
                }

                override fun onMoved(fromPosition: Int, toPosition: Int) {
                    notifyItemMoved(fromPosition, toPosition)
                }

                override fun onInserted(position: Int, count: Int) {
                    notifyItemRangeInserted(position, count)

                }

                override fun onRemoved(position: Int, count: Int) {
                    notifyItemRangeRemoved(position, count)
                }

            })
        }
    //................
}
  • Incremental update using diff

Judge whether the contents are the same in the areContentsTheSame method. If they are the same, the item will not be loaded. If the contents are different, false will be returned, and the data can be updated. Just implement the getChangePayload method

  override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
    return old[oldItemPosition] != new[newItemPosition]
  }
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
      val payload = Bundle()
      payload.putString("key", "${old[oldItemPosition]} = ${new[newItemPosition]} Duplicate value")
      return payload
}

After areContentsTheSame returns false, the following method will be called. Create a valid object in this method, and then return.

When comparing IDS in areItemsTheSame, the content is directly compared. Therefore, when comparing contents, reverse them and update the same contents incrementally (generally, items with the same id and different contents are updated incrementally)

Then modify it in the adapter as follows:

override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) {
    if (payloads.isEmpty()) {
        onBindViewHolder(holder, position)
    } else {
        //Incremental update
        val pay = payloads[0] as Bundle
        val value = pay.get("key") as String
        holder.tvText.text = value
    }
}

onBindViewHolder is a method with three parameters. If there is no increment, the original onBindViewHolder will be called. Otherwise, use incremental data.

The final effect is the last picture above;

Here is just a demonstration of the usage of increment. The specific judgment should be realized by ourselves. The above code is just simple to write and see the effect.

  • If diff is calculated when the list is very different

-Use Thread to send DiffResult to the main Thread -Use RxJava to put the calculateDiff operation in the background thread -Use the asynclistdiff (executor) / ListAdapter provided by Google (the ListAdapter under the recycler package is not the usual adapter). He encapsulated the matter.

All three methods do the same thing, putting the calculation difference on the background thread.

8. ItemDecoration draw the wind division line

​ Take a simple look at the source code

    /**
     * Draw before itemView and it will appear under item
     */
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
        onDraw(c, parent);
    }
    /**
     * Draw on the top of itemView and overlay it
     * @param c Canvas to draw into
     * @param parent RecyclerView this ItemDecoration is drawing into
     * @param state The current state of RecyclerView.
     */
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
            @NonNull State state) {
        onDrawOver(c, parent);
    }

    /**
     * Item Offset of
     */
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
            @NonNull RecyclerView parent, @NonNull State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
}

Combined with the source code of DividerItemDecoration, it can be clear.

Happy coding