Several tips to improve Android ListView performance

Several tips to improve Android ListView performance

Translator’s Note:

1. Since this is a technical article, some words and sentences use the original text for more accurate expression.

2. Due to the limited level of translation, some parts may not be translated accurately. If there are any inappropriate parts, please criticize and correct them.

3. There is no particularly good Chinese translation for the word inflation.

How does ListView work?

ListView is designed for applications that require scalability and high performance. In practice, this means that ListView has the following two requirements:

  1. Create as few Views as possible;
  2. Just draw and layout the subViews that are visible on the screen.

It is easy to understand the first point: creating and displaying Views through layout XML files is an expensive, time-consuming and resource-consuming operation. Although the layout files have been compiled and packaged into binary form for more efficient syntax parsing, creating Views still requires passing a special XML tree and instantiating all Views that need to respond.

ListView solves this problem by recycling some invisible Views, usually referred to as "ScrapView" in the Android source code. This means that developers only need to simply update the content of each row without having to create a View for each individual row layout.

To achieve the second point, when we slide the screen, ListView uses View recycling to increase the Views below or above the current window, and moves the currently active Views to a recyclable pool. In this way, ListView only needs to keep enough Views in memory to fill the layout in the allocated space and some additional recyclable Views, even when your Adapter has hundreds of items to fit. It will use different methods to fill the space between rows, from the top or bottom, etc., depending on how the window changes.

The following figure shows what happens when you press the ListView:

Through the above introduction, we are already familiar with the mechanism of ListView. Let's move on to the tips. As mentioned above, when sliding, ListView dynamically creates and recycles many Views to make the adapter's getView() as lightweight as possible. All the tips are to make getView() faster through various methods.

View recycling

Every time ListView needs to display a new row on the screen, it calls the getView() method from its Adapter. As we all know, the getView() method has three parameters: the row position, convertView, and the parent ViewGroup.

The parameter convertView is actually the ScrapView mentioned earlier. When ListView requires updating the layout of a row, convertView is a non-null value. Therefore, when the convertView value is non-null, you only need to update the content without re-layout a new row. getView() in Adapter is generally in the following form:

  1. public View getView( int position, View convertView, ViewGroup parent) {
  2. if (convertView == null ) {
  3. convertView = mInflater.inflate(R.layout.your_layout, null );
  4. }
  5.  
  6. TextView text = (TextView) convertView.findViewById(R.id.text);
  7. text.setText( "Position " + position);
  8.  
  9. return convertView;
  10. }

How to write a template for View Holder

A very common operation in Android is to find an internal View in the layout file. This is usually implemented using a View method called findViewById(). This findViewById() method is recursively called to find its subtree in the View tree based on a View ID. Although it is completely normal to use findViewById() in static UI layouts. However, when sliding, ListView calls getView() in its Adapter very frequently. findViewById() may affect the performance of ListView when sliding, especially when your row layout is very complex.

Finding an inner view inside an inflated layout is one of the most common operations on Android. This is usually done through a View method called findViewById( ). This method will recursively go through the view tree looking for a child with a given ID code. Using findViewById() for static UI layouts is perfectly fine, but as you can see, the adapter's getView() is called very frequently when scrolling in a ListView . findViewById() may perceivably hit the performance of ListViews, especially scrolling, if your row layouts are non-trivial.

The View Holder pattern is to reduce the number of calls to findViewById() in the getView() method in the Adapter. In fact, View Holder is a lightweight inner class that is used to directly reference all internal views. After creating the View, you can store the View in each row as a label. In this way, you only need to call findViewById() when you first create the layout. The following is a code example of a View Holder template using the above method:

  1. public View getView( int position, View convertView, ViewGroup parent) {
  2. ViewHolder holder;
  3.  
  4. if (convertView == null ) {
  5. convertView = mInflater.inflate(R.layout.your_layout, null );
  6.  
  7. holder = new ViewHolder();
  8. holder.text = (TextView) convertView.findViewById(R.id.text);
  9.  
  10. convertView.setTag(holder);
  11. } else {
  12. holder = convertView.getTag();
  13. }
  14.  
  15. holder.text.setText( "Position " + position);
  16.  
  17. return convertView;
  18. }
  19.  
  20. private   static   class ViewHolder {
  21. public TextView text;
  22. }

Asynchronous loading

Often, Android apps display some multimedia content, such as pictures, in each row of ListView. There is no problem using built-in image resources in the app in getView() in the Adapter, because they can be stored in Android's cache. But when you want to polymorphically display content from the local disk or network, such as thumbnails, resume pictures, etc. In this case, you may not want to load them directly in getView() in the Adapter, because the IO process will block the UI thread. If you do this, the ListView will look very stuck.

If you want to run all row IO operations or any high-load CPU-bound asynchronous operations in a separate thread, the trick is to make it consistent with the recycling behavior of ListView. For example, if you use AsyncTask to load the profile picture in getView() in the Adapter, the image View you are loading may be recycled for use elsewhere before the AsyncTask is completed. Therefore, once the asynchronous operation is completed, a mechanism is needed to know if the corresponding View has been recycled.

A simple way to achieve this is to attach some information that identifies the row and its associated View. Then, when the asynchronous operation is completed, check whether the target row's View is consistent with the identified View. There are many ways to achieve this goal. Here is a very simple example of implementing this method:

  1. public View getView( int position, View convertView,
  2. ViewGroup parent) {
  3. ViewHolder holder;
  4.  
  5. ...
  6.  
  7. holder.position = position;
  8.  
  9. new ThumbnailTask(position, holder)
  10. .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null );
  11.  
  12. return convertView;
  13. }
  14.  
  15. private   static   class ThumbnailTask ​​extends AsyncTask {
  16. private   int mPosition;
  17. private ViewHolder mHolder;
  18.  
  19. public ThumbnailTask( int position, ViewHolder holder) {
  20. mPosition = position;
  21. mHolder = holder;
  22. }
  23.  
  24. @Override  
  25. protected Cursor doInBackground(Void... arg0) {
  26. // Download bitmap here  
  27. }
  28.  
  29. @Override  
  30. protected   void onPostExecute(Bitmap bitmap) {
  31. if (mHolder.position == mPosition) {
  32. mHolder.thumbnail.setImageBitmap(bitmap);
  33. }
  34. }
  35. }
  36.  
  37. private   static   class ViewHolder {
  38. public ImageView thumbnail;
  39. public   int position;
  40. }

Human-computer interaction knowledge

Loading a lot of resources asynchronously in each row is a must for a high-performance ListView. However, if you blindly start an asynchronous operation in every getView() call when sliding the screen, the result is that you will waste a lot of resources. Because rows are frequently recycled, most of the returned results will be discarded.

Considering the actual human-computer interaction, in the ListView adapter, no asynchronous operation should be triggered in each row. In other words, when there is a fling operation in the ListView, it makes no sense to start any asynchronous operation. Once the scrolling stops or is about to stop, it is time to start displaying the content of each row.

I won't post a code example here because it's too much code. A great example is in the classic Shelves app by Romain Guy. When the GridView stops scrolling and does nothing else, it triggers to asynchronously load the book cover resources. You can show cached content even while scrolling, balancing interactions with the memory cache. What a great idea!

above

I highly recommend you check out Romain Guy and Adam Powell's discussion of ListView, which covers a lot of the material in this post. You can check out Pattrn to see how several of these techniques are used in real-world applications.

Hope it will be a useful reference for you in your Android development :–)

Long Luo at PM17:30 Feb. 14th, 2014 @Shenzhen, China.

original link:http://longluo.github.io/blog/20140214/some_tips_about_android_listview_performence/

written by Frank Luo posted at http://longluo.github.io

Translated by Long Luo

Original link: Performance Tips for Android's ListView

<<:  Which three “mobile industry chains” has WeChat created?

>>:  iOS Programming Basics: How does the Hello World App work?

Recommend

Agan talks about cross-border Amazon operations

Cross-border learning is not just a PPT with audi...

Why does my voice sound so bad in the voice chat? Here’s the reason!

After sending a voice message on WeChat, will you...

Frostbite! What should I do?

What is frostbite? Which department should I go t...

Understand the latest developments in AI in one article! A guide for workers →

In March 2023, AI technology represented by GPT-4...

In-depth analysis: How to operate 2B products

A large number of companies and products have bee...

What are the network promotion outsourcing companies in Chengdu?

There are many online marketing companies, but th...

10,000 words to dismantle the L’Oreal brand!

As the wave of new brands cools down, the spotlig...