ListView asynchronously loading pictures out of order, cause analysis and solution

Posted by k9underdog on Mon, 31 Jan 2022 03:56:46 +0100

Among the controls of all Android systems, the ListView control has a complex usage. The key is that even if the usage is complex, it often has some strange problems, which makes people very headache. For example, loading pictures in ListView is OK if you load pictures synchronously, but once you use asynchronous loading pictures, there will be a problem. I believe many Android developers have encountered this problem, that is, asynchronous loading pictures will be out of place and out of order. When encountering this problem, many people search online and find the corresponding solutions, but I'm afraid not many people really understand the causes of this problem and solve it symptomatically. So today, let's specifically and deeply analyze the reasons for the disorder of ListView's asynchronous loading pictures and how to apply the right medicine to solve it.

The principle of this article is based on the previous article. If you don't know enough about the working principle of ListView, it is recommended to read the complete analysis of the working principle of Android ListView first, so that you can fully understand it from the perspective of source code.

Problem recurrence

To solve the problem, we first need to reproduce the problem. Here, we only need to build a basic ListView project, and then asynchronously request pictures and display them in the ListView. The problem can be reproduced. Then we will create a ListViewTest project.

After the project is completed, the first problem to be solved is the problem of data source. Since the ListView needs to request pictures from the network, I prepare many pictures in advance, upload them to my CSDN album, and then create a new Images class to configure the URL addresses of all the pictures in the album. The code is as follows:

/**
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553
 * @author guolin
 */
public class Images {
 
    public final static String[] imageUrls = new String[] {
    	"https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",  
        "https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
        "https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
        "https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
        "https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",
    };
}

After setting up the image source, we need a ListView to display all the images. Open or modify an activity_ main. The code in XML is as follows:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">
 
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </ListView>
It's very simple. It's just a ListView written in LinearLayout. Next, we need to define the layout of each sub View in the ListView and create a new image_item.xml layout, add the following code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:src="@drawable/empty_photo" 
        android:scaleType="fitXY"/>
 
</LinearLayout>

It's still simple, image_ item. There is only one ImageView control in the XML layout, which is used to display pictures. The control will display an empty by default_ photo. In this way, we have written all the layout documents.

Next, create a new ImageAdapter as the adapter of ListView. The code is as follows:

/** 
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553 
 * @author guolin 
 */  
public class ImageAdapter extends ArrayAdapter<String> {
 


ImageAdapter The code in is relatively simple getView()Method first obtains the image according to the current position URL Address, and then use inflate()Method loading image_item.xml This layout and get ImageView Control, and then open a BitmapWorkerTask Asynchronous task to load pictures from the network, and finally set the loaded pictures to ImageView above. Note that in order to prevent the picture from taking up too much memory, we still use it LruCache Technology for memory control. Friends who are not familiar with this technology can refer to my previous article Android Efficient loading of large and multi graph solutions to effectively avoid the application OOM . 



Finally, the code of the main interface of the program is very simple and can be modified MainActivity The code in is as follows:

/**  
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553  
 * @author guolin  
 */    
public class MainActivity extends Activity {
	
	private ListView listView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		listView = (ListView) findViewById(R.id.list_view);
		ImageAdapter adapter = new ImageAdapter(this, 0, Images.imageThumbUrls);
		listView.setAdapter(adapter);
	}
 
 
}
This is all the code of the whole program. Remember to AndroidManifest.xml Add in INTERNET jurisdiction.


So the idea of the current program is actually very simple. We are ListView of getView()Method to obtain a picture from the network. When the picture is obtained successfully, the picture is displayed to the ImageView above. It doesn't look like a problem, does it? Now let's run the program to see the effect.







okay? How can it be like this when sliding ListView The picture will automatically change back and forth, and the position of the picture is incorrect. It's almost a mess! But all our logic is very simple. How can this kind of picture change around automatically? Unfortunately, this is due to Listview Caused by the internal working mechanism, if you are right Listview If you don't understand the working mechanism of, it will be difficult to understand this phenomenon, but fortunately, I explained it in the last article ListView So let's analyze the causes of this problem.



Cause analysis

As mentioned in the previous article, ListView It is possible to load hundreds of data without OOM,The most important thing is its excellent internal implementation mechanism. Although as ordinary users, we don't have to care ListView How to realize the internal, but after you understand its internal principle, many problems that were difficult to explain have become justified.



ListView With the help of RecycleBin No matter how many data producers need to display, ListView Child in View In fact, there are only a few back and forth, moving out of the screen View The data moved into the screen will be reused soon. The schematic diagram is as follows:







So here we can think about it. At present, there are about 60 pictures in the data source URL Address, and according to ListView Obviously, it is impossible to assign a single image to each image ImageView Control, ImageView In fact, the number of controls is a little more than the number of pictures that can be displayed on one screen ImageView Control will enter RecycleBin And the new elements entering the screen will RecycleBin Get in ImageView Control.



Then, whenever a new element enters the interface, it will be called back getView()Method, and in getView()The method will enable asynchronous request to obtain pictures from the network. Note that network operations are time-consuming, that is, when we slide quickly ListView It is very likely that the element in a certain position will start to request pictures from the network after entering the screen, but it will be removed from the screen before the picture download is completed. What kind of phenomenon will occur in this case? according to ListView The control removed from the screen will soon be reused by the elements newly entering the screen. If the previously initiated picture request is responded at this time, the pictures in the previous position will be displayed in the current position, because although their positions are different, they all share the same image ImageView Example, so the picture is out of order.



But it's not finished yet. For the new element entering the screen, it will also launch a network request to obtain the image at the current location. When the image is downloaded, it will be set to the same location ImageView Above, therefore, there will be a situation that one picture is displayed first and then another picture is displayed. Then the picture we just saw will automatically change from one picture to another, which can be explained.



The cause of the problem has been analyzed, but how to solve it? tell the truth, ListView There is no standard solution to the problem of asynchronously loading pictures. Many people have their own set of solutions. Here I am going to explain three classic solutions to you. You can solve this problem through any one, but every time we learn one more idea, our level can be further improved.



Solution I use findViewWithTag


findViewWithTag It is a relatively simple and easy to understand solution. In fact, it was long ago Android Photo wall application implementation, no matter how many pictures are not afraid of collapse. In this article, I adopted it findViewWithTag To avoid the disorder of the picture. So let's first look at how to solve this problem by modifying the code, and then study it findViewWithTag How it works.



use findViewWithTag You don't need to change too much code, just change it ImageAdapter This class is OK, as shown below:

/** 
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553 
 * @author guolin 
 */  
public class ImageAdapter extends ArrayAdapter<String> {
	
	private ListView mListView; 
 
	......
 
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if (mListView == null) {  
            mListView = (ListView) parent;  
        } 
		String url = getItem(position);
		View view;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
		} else {
			view = convertView;
		}
		ImageView image = (ImageView) view.findViewById(R.id.image);
		image.setImageResource(R.drawable.empty_photo);
        image.setTag(url);
		BitmapDrawable drawable = getBitmapFromMemoryCache(url);
		if (drawable != null) {
			image.setImageDrawable(drawable);
		} else {
			BitmapWorkerTask task = new BitmapWorkerTask();
			task.execute(url);
		}
		return view;
	}
 
	......
 
	/**
	 * The task of asynchronously downloading pictures.
	 * 
	 * @author guolin
	 */
	class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
 
		String imageUrl; 
 
		@Override
		protected BitmapDrawable doInBackground(String... params) {
			imageUrl = params[0];
			// Start downloading pictures in the background
			Bitmap bitmap = downloadBitmap(imageUrl);
			BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
			addBitmapToMemoryCache(imageUrl, drawable);
			return drawable;
		}
 
		@Override
		protected void onPostExecute(BitmapDrawable drawable) {
			ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);  
            if (imageView != null && drawable != null) {  
                imageView.setImageDrawable(drawable);  
            } 
		}
 
		......
 
	}
 
}
There are only so many changes, so let's analyze it. Due to use findViewWithTag Must have ListView An example of, then we're Adapter How can I get it ListView What about the example of? In fact, if you read through the last article carefully, you can know, getView()The third parameter passed in the method is actually ListView So here we define a global variable mListView,Then in getView()Method to determine whether it is empty. If it is empty, set parent This parameter is assigned to it.



In addition getView()Method, which is called ImageView of setTag()Method and put the current position of the picture URL The address is passed in as a parameter. This is for the follow-up findViewWithTag()Methods to prepare.



Finally, we modified BitmapWorkerTask The constructor of is no longer used here ImageView The instance of is passed in, but in onPostExecute()Method passed ListView of findVIewWithTag()Method to get ImageView Control. After obtaining the control instance, judge whether the next is empty. If not, let the picture be displayed on the control.



Here we can try to analyze findViewWithTag In fact, as the name suggests, this method is through Tag To get the name of the Tag Name of the control, we first need to call the setTag()Method to set a Tag,Then call ListView of findViewWithTag()Methods use the same Tag Name to retrieve the control.



So why findViewWithTag()After the method, the picture will no longer be out of order? In fact, the reason is very simple, because ListView Medium ImageView Controls are reused. The controls that move out of the screen will soon be reused by the pictures that enter the screen getView()The method will be executed again, and in getView()Method will be used for this ImageView Control to set a new Tag,So old Tag It will be overwritten, so it will be called again at this time findVIewWithTag()Method and pass in the old Tag,You can only get null And we only judge ImageView Not equal to null The picture will be set only when the image is out of order, so that the problem of picture disorder does not exist.



This is the first solution.



Solution 2: use weak reference Association


Although I call this solution weak reference association here, in fact, weak reference is only an auxiliary means. The most important thing is association. The essence of this solution is to make ImageView and BitmapWorkerTask Establish a two-way association between them, hold each other's references, and then solve the problem of picture disorder through appropriate logical judgment. Then, in order to prevent memory leakage, the two-way association should be established by weak reference. Compared with the first solution, the second solution is significantly more complex, but it will perform better in terms of performance and efficiency.



We still only need to change ImageAdapter But there are many changes this time, so I put ImageAdapter All the codes in are posted as follows:

/** 
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553 
 * @author guolin 
 */  
public class ImageAdapter extends ArrayAdapter<String> {
	
	private ListView mListView; 
	
	private Bitmap mLoadingBitmap;
 
	/**
	 * The core class of image caching technology is used to cache all downloaded images. When the program memory reaches the set value, the least recently used images will be removed.
	 */
	private LruCache<String, BitmapDrawable> mMemoryCache;
 
	public ImageAdapter(Context context, int resource, String[] objects) {
		super(context, resource, objects);
		mLoadingBitmap = BitmapFactory.decodeResource(context.getResources(),
				R.drawable.empty_photo);
		// Gets the maximum available memory for the application
		int maxMemory = (int) Runtime.getRuntime().maxMemory();
		int cacheSize = maxMemory / 8;
		mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
			@Override
			protected int sizeOf(String key, BitmapDrawable drawable) {
				return drawable.getBitmap().getByteCount();
			}
		};
	}
 
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if (mListView == null) {  
            mListView = (ListView) parent;  
        } 
		String url = getItem(position);
		View view;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
		} else {
			view = convertView;
		}
		ImageView image = (ImageView) view.findViewById(R.id.image);
		BitmapDrawable drawable = getBitmapFromMemoryCache(url);
		if (drawable != null) {
			image.setImageDrawable(drawable);
		} else if (cancelPotentialWork(url, image)) {
			BitmapWorkerTask task = new BitmapWorkerTask(image);
			AsyncDrawable asyncDrawable = new AsyncDrawable(getContext()
					.getResources(), mLoadingBitmap, task);
			image.setImageDrawable(asyncDrawable);
			task.execute(url);
		}
		return view;
	}
	
	/**
	 * A customized Drawable that holds the weak reference of BitmapWorkerTask.
	 */
	class AsyncDrawable extends BitmapDrawable {
 
		private WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
 
		public AsyncDrawable(Resources res, Bitmap bitmap,
				BitmapWorkerTask bitmapWorkerTask) {
			super(res, bitmap);
			bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
					bitmapWorkerTask);
		}
 
		public BitmapWorkerTask getBitmapWorkerTask() {
			return bitmapWorkerTaskReference.get();
		}
 
	}
	
	/**
	 * Get the BitmapWorkerTask corresponding to the incoming ImageView.
	 */
	private BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
        if (imageView != null) {
            Drawable drawable = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable) {
                AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
        }
        return null;
    }
	
	/**
	 * Cancel the potential tasks in the background. When it is considered that there is another image request task in the current ImageView
	 * ,Cancel it and return true, otherwise return false.
	 */
    public boolean cancelPotentialWork(String url, ImageView imageView) {
        BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
        if (bitmapWorkerTask != null) {
            String imageUrl = bitmapWorkerTask.imageUrl;
            if (imageUrl == null || !imageUrl.equals(url)) {
                bitmapWorkerTask.cancel(true);
            } else {
                return false;
            }
        }
        return true;
    }
 
	/**
	 * Store a picture in LruCache.
	 * 
	 * @param key
	 *            LruCache The URL address of the picture is passed in here.
	 * @param drawable
	 *            LruCache The BitmapDrawable object downloaded from the network is passed in here.
	 */
	public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {
		if (getBitmapFromMemoryCache(key) == null) {
			mMemoryCache.put(key, drawable);
		}
	}
 
	/**
	 * Get a picture from LruCache and return null if it does not exist.
	 * 
	 * @param key
	 *            LruCache The URL address of the picture is passed in here.
	 * @return BitmapDrawable object corresponding to the incoming key, or null.
	 */
	public BitmapDrawable getBitmapFromMemoryCache(String key) {
		return mMemoryCache.get(key);
	}
 
	/**
	 * The task of asynchronously downloading pictures.
	 * 
	 * @author guolin
	 */
	class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
 
		String imageUrl; 
		
		private WeakReference<ImageView> imageViewReference;
		
		public BitmapWorkerTask(ImageView imageView) {  
			imageViewReference = new WeakReference<ImageView>(imageView);
        }  
 
		@Override
		protected BitmapDrawable doInBackground(String... params) {
			imageUrl = params[0];
			// Start downloading pictures in the background
			Bitmap bitmap = downloadBitmap(imageUrl);
			BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
			addBitmapToMemoryCache(imageUrl, drawable);
			return drawable;
		}
 
		@Override
		protected void onPostExecute(BitmapDrawable drawable) {
			ImageView imageView = getAttachedImageView();
            if (imageView != null && drawable != null) {  
                imageView.setImageDrawable(drawable);  
            } 
		}
		
		/**
		 * Gets the ImageView associated with the current BitmapWorkerTask.
		 */
		private ImageView getAttachedImageView() {
            ImageView imageView = imageViewReference.get();
            BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask) {
                return imageView;
            }
            return null;
        }
 
		/**
		 * Create an HTTP request and get the Bitmap object.
		 * 
		 * @param imageUrl
		 *            URL address of the picture
		 * @return Parsed Bitmap object
		 */
		private Bitmap downloadBitmap(String imageUrl) {
			Bitmap bitmap = null;
			HttpURLConnection con = null;
			try {
				URL url = new URL(imageUrl);
				con = (HttpURLConnection) url.openConnection();
				con.setConnectTimeout(5 * 1000);
				con.setReadTimeout(10 * 1000);
				bitmap = BitmapFactory.decodeStream(con.getInputStream());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (con != null) {
					con.disconnect();
				}
			}
			return bitmap;
		}
 
	}
 
}
So let's start to analyze it bit by bit. First of all, ImageView and BitmapWorkerTask A two-way weak reference association should be established between them, which has been established in the above code. ImageView You can get its corresponding BitmapWorkerTask,and BitmapWorkerTask You can also get its corresponding ImageView. 


Let's take a look at how this bidirectional weak reference association is established. BitmapWorkerTask point ImageView The weak reference Association of is relatively simple, that is, in BitmapWorkerTask Add a constructor in the constructor and require the input of ImageView This parameter. But we no longer hold it directly ImageView Instead, use WeakReference yes ImageView A layer of packaging was carried out so that OK Yes.



however ImageView point BitmapWorkerTask Weak reference Association of is not so easy, because it is difficult for us to BitmapWorkerTask A weak reference of is set directly to ImageView among. What should I do? A clever method is used here, that is, with the help of customization Drawable The way to achieve. As you can see, we have customized one AsyncDrawable Class and let it inherit from BitmapDrawable,Then rewrite it AsyncDrawable In the constructor, it is required to BitmapWorkerTask Pass in and wrap it with a layer of weak references here. So now AsyncDrawable point BitmapWorkerTask There's already a connection, but ImageView point BitmapWorkerTask The association of does not exist yet. What should we do? It's simple, let ImageView and AsyncDrawable Just associate it again. As you can see, in getView()Method, we call ImageView of setImageDrawable()Method AsyncDrawable Set it in, then ImageView You can pass getDrawable()Method to get the information associated with it AsyncDrawable,Then with the help of AsyncDrawable You can get BitmapWorkerTask Yes. such ImageView point BitmapWorkerTask The weak reference Association of was also successfully established.



Now that the association of two-way weak references has been established, the next step is logical judgment. So how to avoid the disorder of pictures through logical judgment? Here we introduce two methods, one is getBitmapWorkerTask()Method, which can be based on the passed in ImageView To get its corresponding BitmapWorkerTask,The internal logic is to get it first ImageView Corresponding AsyncDrawable,Reacquire AsyncDrawable Corresponding BitmapWorkerTask. The other is getAttachedImageView()Method, which will get the current BitmapWorkerTask Associated ImageView,Then call getBitmapWorkerTask()Method to get the ImageView Corresponding BitmapWorkerTask,Finally, judge if the obtained BitmapWorkerTask be equal to this,That is, the current BitmapWorkerTask,Then it will ImageView Return, otherwise return null. Finally, in onPostExecute()Among the methods, you only need to use getAttachedImageView()Method ImageView Just show the picture.



So why can the problem of picture disorder be solved after making this logical judgment? In fact, the main mystery is getAttachedImageView()Method, which uses the current BitmapWorkerTask Associated ImageView To get this in reverse ImageView Associated BitmapWorkerTask,Then use these two BitmapWorkerTask Make a comparison, if it is found to be the same BitmapWorkerTask Will return ImageView,Otherwise, return null. So under what circumstances are these two BitmapWorkerTask Will it be different? For example, if a picture is removed from the screen, its ImageView If it is reused by another new image entering the screen, this will be given ImageView Associate a new BitmapWorkerTask,In this case, the previous BitmapWorkerTask And new BitmapWorkerTask It must not be equal. At this time getAttachedImageView()Method returns null,And we judge ImageView be equal to null The picture will not be set, so there will be no picture disorder.



In addition, there is another method that deserves your attention cancelPotentialWork()Method, this method can greatly improve the whole ListView Picture loading efficiency. This method receives two parameters, one for the image url,One ImageView. Take a look at its internal logic. First, it is also called getBitmapWorkerTask()Method to get the passed in ImageView Corresponding BitmapWorkerTask,Next take BitmapWorkerTask Medium imageUrl And incoming url Make a comparison if two url Call if you don't wait BitmapWorkerTask of cancel()Method, and then return true,If two url If equal, return false. 



So what does this logic mean? It's not complicated, two url When comparing, if it is found to be the same, it means that the requested image is the same, then it will be returned directly false,So it won't start again BitmapWorkerTask To request pictures, and if two url Different, that means this ImageView It is reused by another picture, which is called at this time BitmapWorkerTask of cancel()Method cancels the previous request and then restarts BitmapWorkerTask Come and ask for new pictures. With this operation protection, you can filter out some invalid picture requests that have been removed from the screen, so as to improve the overall performance ListView Efficiency of loading pictures.



This is the second solution.



Solution 3 use NetworkImageView


The first two solutions require us to do additional logical processing, because ImageView Itself can not automatically solve this problem, but if we use NetworkImageView This control is very simple. It has taken this problem into account. We can use it directly without any additional processing, and there will be no picture disorder.



NetworkImageView yes Volley For the control provided in it, I wrote a blog to explain it before. Friends who are not familiar with the control can read it first Android Volley Complete resolution(two),use Volley Load network pictures.



Now let's see how to use it NetworkImageView To solve this problem, we need to modify it first image_item.xml File because we no longer use it ImageView Control, the code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:src="@drawable/empty_photo" 
        android:scaleType="fitXY"/>
 
</LinearLayout>
It's simple, just put ImageView Replaced with NetworkImageView. Then modify ImageAdapter The code in is as follows:
/**
 * Original address: http://blog.csdn.net/guolin_blog/article/details/45586553
 * @author guolin
 */
public class ImageAdapter extends ArrayAdapter<String> {
	
	ImageLoader mImageLoader;
 
	public ImageAdapter(Context context, int resource, String[] objects) {
		super(context, resource, objects);
		RequestQueue queue = Volley.newRequestQueue(context);
		mImageLoader = new ImageLoader(queue, new BitmapCache());
	}
 
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		String url = getItem(position);
		View view;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
		} else {
			view = convertView;
		}
		NetworkImageView image = (NetworkImageView) view.findViewById(R.id.image);
		image.setDefaultImageResId(R.drawable.empty_photo);
		image.setErrorImageResId(R.drawable.empty_photo);
		image.setImageUrl(url, mImageLoader);
		return view;
	}
 
	/**
	 * Use LruCache to cache pictures
	 */
	public class BitmapCache implements ImageCache {
 
		private LruCache<String, Bitmap> mCache;
 
		public BitmapCache() {
			// Gets the maximum available memory for the application
			int maxMemory = (int) Runtime.getRuntime().maxMemory();
			int cacheSize = maxMemory / 8;
			mCache = new LruCache<String, Bitmap>(cacheSize) {
				@Override
				protected int sizeOf(String key, Bitmap bitmap) {
					return bitmap.getRowBytes() * bitmap.getHeight();
				}
			};
		}
 
		@Override
		public Bitmap getBitmap(String url) {
			return mCache.get(url);
		}
 
		@Override
		public void putBitmap(String url, Bitmap bitmap) {
			mCache.put(url, bitmap);
		}
 
	}
 
}
Yes, it's that simple. A total of about 60 lines of code will do everything! We don't need to write another one ourselves BitmapWorkerTask To handle the download and display of pictures, and you don't need to manage it yourself LruCache Logic, everything NetworkImageView It's all done for us. As for the above code, I won't explain it anymore, because it's too simple.


Well, of course, although there is no additional logical processing, there will be no picture disorder at all, because NetworkImageView It's all disposed of internally for us. But you may be curious, NetworkImageView How did you do it? Then let's analyze its source code.



NetworkImageView The code to start loading pictures in is setImageUrl()Method, the source code analysis starts from here, as follows:

/**
 * Sets URL of the image that should be loaded into this view. Note that calling this will
 * immediately either set the cached image (if available) or the default image specified by
 * {@link NetworkImageView#setDefaultImageResId(int)} on the view.
 *
 * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and
 * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling
 * this function.
 *
 * @param url The URL that should be loaded into this ImageView.
 * @param imageLoader ImageLoader that will be used to make the request.
 */
public void setImageUrl(String url, ImageLoader imageLoader) {
    mUrl = url;
    mImageLoader = imageLoader;
    // The URL has potentially changed. See if we need to load it.
    loadImageIfNecessary(false);
}
setImageUrl()There are few lines of code in the method, which is worth noting loadImageIfNecessary()It seems that the logic of loading pictures is carried out here. Let's go in and have a look:
/**
 * Loads the image for the view if it isn't already loaded.
 * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
 */
private void loadImageIfNecessary(final boolean isInLayoutPass) {
    int width = getWidth();
    int height = getHeight();
 
    boolean isFullyWrapContent = getLayoutParams() != null
            && getLayoutParams().height == LayoutParams.WRAP_CONTENT
            && getLayoutParams().width == LayoutParams.WRAP_CONTENT;
    // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
    // view, hold off on loading the image.
    if (width == 0 && height == 0 && !isFullyWrapContent) {
        return;
    }
 
    // if the URL to be loaded in this view is empty, cancel any old requests and clear the
    // currently loaded image.
    if (TextUtils.isEmpty(mUrl)) {
        if (mImageContainer != null) {
            mImageContainer.cancelRequest();
            mImageContainer = null;
        }
        setDefaultImageOrNull();
        return;
    }
 
    // if there was an old request in this view, check if it needs to be canceled.
    if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
        if (mImageContainer.getRequestUrl().equals(mUrl)) {
            // if the request is from the same URL, return.
            return;
        } else {
            // if there is a pre-existing request, cancel it if it's fetching a different URL.
            mImageContainer.cancelRequest();
            setDefaultImageOrNull();
        }
    }
 
    // The pre-existing content of this view didn't match the current URL. Load the new image
    // from the network.
    ImageContainer newContainer = mImageLoader.get(mUrl,
            new ImageListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    if (mErrorImageId != 0) {
                        setImageResource(mErrorImageId);
                    }
                }
 
                @Override
                public void onResponse(final ImageContainer response, boolean isImmediate) {
                    // If this was an immediate response that was delivered inside of a layout
                    // pass do not set the image immediately as it will trigger a requestLayout
                    // inside of a layout. Instead, defer setting the image by posting back to
                    // the main thread.
                    if (isImmediate && isInLayoutPass) {
                        post(new Runnable() {
                            @Override
                            public void run() {
                                onResponse(response, false);
                            }
                        });
                        return;
                    }
 
                    if (response.getBitmap() != null) {
                        setImageBitmap(response.getBitmap());
                    } else if (mDefaultImageId != 0) {
                        setImageResource(mDefaultImageId);
                    }
                }
            });
 
    // update the ImageContainer to be the new bitmap container.
    mImageContainer = newContainer;
}
This is called on line 43 ImageLoader of get()Method to request pictures, get()Method returns a ImageContainer Object, which encapsulates the image request address Bitmap Wait for data, each NetworkImageView Will correspond to one in ImageContainer. Then on line 31, we see that here from ImageContainer Get the encapsulated picture request address from the object and compare it with the current request address. If it is the same, it means that this is a duplicate request, so you can directly return Drop, call if different cancelRequest()Method cancels the request, then sets the picture as the default picture and re initiates the request.


So the core logic to solve the picture disorder is here. In fact NetworkImageView The solution is relatively simple. If the control has been removed from the screen and reused, cancel the previous request, that's all.



And we all know that under normal circumstances, it may not solve the problem just by doing so, because Java There is no guarantee that threads can be interrupted, even as used in the second solution BitmapWorkerTask of cancel()Method can not guarantee that the request can be cancelled, so the processing method of weak reference association needs to be used. But in NetworkImageView You can be so capricious, just call cancelRequest()Method can cancel the request, which is mainly due to Volley Excellent design. because Volley The encapsulation of the network is very excellent. It can ensure that as long as the request is cancelled, it will never be recalled. Since it will not be recalled, it will not be returned NetworkImageView Naturally, there will be no disorder.



It should be noted that, Volley It just guarantees that the cancelled request will not be called back, but it does not say that any request can be interrupted. It can be seen that even Volley It is also impossible to interrupt an executing thread. If a thread is executing, Volley It is only guaranteed that there will be no callback after it is executed, but in the view of the caller, it is as if the request has been cancelled.



So here we only analyze the source code related to the disordered order of pictures. If you want to know about Volley For more source code, please refer to my previous article Android Volley Complete resolution(four),Take you to understand from the perspective of source code Volley . 



This is the third solution.



All right, about ListView The problem of asynchronous loading picture disorder is discussed here today. If you understand the three solutions clearly, you can study this problem more thoroughly. The next article is still ListView Topic, we will learn how to ListView Control for some function expansion, interested friends, please continue to read Android ListView Function expansion to realize high-performance waterfall flow layout.