ListView load pictures asynchronously

  • 2020-05-09 19:19:51
  • OfStack

In the application of APP, the asynchronous loading method of listview can bring a good user experience, and it is also an important indicator to consider the performance of the program. There are a lot of examples on the web about asynchronous loading of listview. The main idea is the same, but many versions have bug or performance issues to be optimized. In view of this, I found a relatively ideal version on the Internet and on this basis to carry on the transformation, let below elaborate its principle in order to explore the mystery, with you all...

Post an effect picture first:
The basic idea of loading pictures asynchronously:
1. Get the image display (memory buffer) from the memory cache first
2. If you cannot get it, get it from SD card (SD card buffer)
3. If none of them are available, download the image from the network and save it to SD card. At the same time, add the image into the memory and display it (whether to display it as the case may be).
OK, enter the code of adapter first:
 
public class LoaderAdapter extends BaseAdapter{ 
private static final String TAG = "LoaderAdapter"; 
private boolean mBusy = false; 
public void setFlagBusy(boolean busy) { 
this.mBusy = busy; 
} 
private ImageLoader mImageLoader; 
private int mCount; 
private Context mContext; 
private String[] urlArrays; 
public LoaderAdapter(int count, Context context, String []url) { 
this.mCount = count; 
this.mContext = context; 
urlArrays = url; 
mImageLoader = new ImageLoader(context); 
} 
public ImageLoader getImageLoader(){ 
return mImageLoader; 
} 
@Override 
public int getCount() { 
return mCount; 
} 
@Override 
public Object getItem(int position) { 
return position; 
} 
@Override 
public long getItemId(int position) { 
return position; 
} 
@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
ViewHolder viewHolder = null; 
if (convertView == null) { 
convertView = LayoutInflater.from(mContext).inflate( 
R.layout.list_item, null); 
viewHolder = new ViewHolder(); 
viewHolder.mTextView = (TextView) convertView 
.findViewById(R.id.tv_tips); 
viewHolder.mImageView = (ImageView) convertView 
.findViewById(R.id.iv_image); 
convertView.setTag(viewHolder); 
} else { 
viewHolder = (ViewHolder) convertView.getTag(); 
} 
String url = ""; 
url = urlArrays[position % urlArrays.length]; 
viewHolder.mImageView.setImageResource(R.drawable.ic_launcher); 
if (!mBusy) { 
mImageLoader.DisplayImage(url, viewHolder.mImageView, false); 
viewHolder.mTextView.setText("--" + position 
+ "--IDLE ||TOUCH_SCROLL"); 
} else { 
mImageLoader.DisplayImage(url, viewHolder.mImageView, true); 
viewHolder.mTextView.setText("--" + position + "--FLING"); 
} 
return convertView; 
} 
static class ViewHolder { 
TextView mTextView; 
ImageView mImageView; 
} 
} 

The key code is the DisplayImage method of ImageLoader, and the implementation of ImageLoader
 
public class ImageLoader { 
private MemoryCache memoryCache = new MemoryCache(); 
private AbstractFileCache fileCache; 
private Map<ImageView, String> imageViews = Collections 
.synchronizedMap(new WeakHashMap<ImageView, String>()); 
//  The thread pool  
private ExecutorService executorService; 
public ImageLoader(Context context) { 
fileCache = new FileCache(context); 
executorService = Executors.newFixedThreadPool(5); 
} 
//  The main method  
public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) { 
imageViews.put(imageView, url); 
//  Look in the memory cache first  
Bitmap bitmap = memoryCache.get(url); 
if (bitmap != null) 
imageView.setImageBitmap(bitmap); 
else if (!isLoadOnlyFromCache){ 
//  If not, start a new thread to load the image  
queuePhoto(url, imageView); 
} 
} 
private void queuePhoto(String url, ImageView imageView) { 
PhotoToLoad p = new PhotoToLoad(url, imageView); 
executorService.submit(new PhotosLoader(p)); 
} 
private Bitmap getBitmap(String url) { 
File f = fileCache.getFile(url); 
//  Look in the file cache to see if there is one  
Bitmap b = null; 
if (f != null && f.exists()){ 
b = decodeFile(f); 
} 
if (b != null){ 
return b; 
} 
//  Finally from the specified url Download image in  
try { 
Bitmap bitmap = null; 
URL imageUrl = new URL(url); 
HttpURLConnection conn = (HttpURLConnection) imageUrl 
.openConnection(); 
conn.setConnectTimeout(30000); 
conn.setReadTimeout(30000); 
conn.setInstanceFollowRedirects(true); 
InputStream is = conn.getInputStream(); 
OutputStream os = new FileOutputStream(f); 
CopyStream(is, os); 
os.close(); 
bitmap = decodeFile(f); 
return bitmap; 
} catch (Exception ex) { 
Log.e("", "getBitmap catch Exception...\nmessage = " + ex.getMessage()); 
return null; 
} 
} 
// decode The image is scaled to reduce memory consumption, and the virtual machine has a limit on the size of the cache for each image  
private Bitmap decodeFile(File f) { 
try { 
// decode image size 
BitmapFactory.Options o = new BitmapFactory.Options(); 
o.inJustDecodeBounds = true; 
BitmapFactory.decodeStream(new FileInputStream(f), null, o); 
// Find the correct scale value. It should be the power of 2. 
final int REQUIRED_SIZE = 100; 
int width_tmp = o.outWidth, height_tmp = o.outHeight; 
int scale = 1; 
while (true) { 
if (width_tmp / 2 < REQUIRED_SIZE 
|| height_tmp / 2 < REQUIRED_SIZE) 
break; 
width_tmp /= 2; 
height_tmp /= 2; 
scale *= 2; 
} 
// decode with inSampleSize 
BitmapFactory.Options o2 = new BitmapFactory.Options(); 
o2.inSampleSize = scale; 
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
} catch (FileNotFoundException e) { 
} 
return null; 
} 
// Task for the queue 
private class PhotoToLoad { 
public String url; 
public ImageView imageView; 
public PhotoToLoad(String u, ImageView i) { 
url = u; 
imageView = i; 
} 
} 
class PhotosLoader implements Runnable { 
PhotoToLoad photoToLoad; 
PhotosLoader(PhotoToLoad photoToLoad) { 
this.photoToLoad = photoToLoad; 
} 
@Override 
public void run() { 
if (imageViewReused(photoToLoad)) 
return; 
Bitmap bmp = getBitmap(photoToLoad.url); 
memoryCache.put(photoToLoad.url, bmp); 
if (imageViewReused(photoToLoad)) 
return; 
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad); 
//  The updated operation is placed UI In the thread  
Activity a = (Activity) photoToLoad.imageView.getContext(); 
a.runOnUiThread(bd); 
} 
} 
/** 
*  Prevent picture misplacement  
* 
* @param photoToLoad 
* @return 
*/ 
boolean imageViewReused(PhotoToLoad photoToLoad) { 
String tag = imageViews.get(photoToLoad.imageView); 
if (tag == null || !tag.equals(photoToLoad.url)) 
return true; 
return false; 
} 
//  For use in UI Update interface in thread  
class BitmapDisplayer implements Runnable { 
Bitmap bitmap; 
PhotoToLoad photoToLoad; 
public BitmapDisplayer(Bitmap b, PhotoToLoad p) { 
bitmap = b; 
photoToLoad = p; 
} 
public void run() { 
if (imageViewReused(photoToLoad)) 
return; 
if (bitmap != null) 
photoToLoad.imageView.setImageBitmap(bitmap); 
} 
} 
public void clearCache() { 
memoryCache.clear(); 
fileCache.clear(); 
} 
public static void CopyStream(InputStream is, OutputStream os) { 
final int buffer_size = 1024; 
try { 
byte[] bytes = new byte[buffer_size]; 
for (;;) { 
int count = is.read(bytes, 0, buffer_size); 
if (count == -1) 
break; 
os.write(bytes, 0, count); 
} 
} catch (Exception ex) { 
Log.e("", "CopyStream catch Exception..."); 
} 
} 
} 

First load from memory, if not, start the thread to get from SD card or network, here notice that the image from SD card is executed in the child thread, otherwise it will not be smooth enough to slide the screen fast, this is optimization 1. At the same time, there is a variable busy in adapter, which means whether listview is in the sliding state or not. If it is in the sliding state, it will only get pictures from the memory. If it is not, there is no need to open a thread to get pictures from external storage or network. Threads in ImageLoader use thread pool, so as to avoid the frequent creation and destruction of multiple threads, some children's shoes always new1 thread to execute this is very undesirable, good 1 point of AsyncTask class, in fact, internal also used thread pool. When you get the image from the network, you first save it to the sd card and then load it into memory. The advantage of this is that you can do a compression process when loading into memory to reduce the memory occupied by the image. This is optimization 3.

And image mismatch problem stems from the nature of our listview cache convertView, hypothesis 1 scenario, a nine item listview1 display, then pull the tenth item, in fact the item is to reuse the first item, that is to say, in the first item download pictures from the network and eventually to display the item has actually won't be in the current display area, displayed the consequences will be in May on the tenth item output image, This leads to the problem of misplaced images. So the solution is to show what's visible, and not show what's invisible. In ImageLoader, there is an map object of imageViews, which is used to save the url set corresponding to the image in the current display area. It can be processed 1 time before display.
Now let's talk about the memory buffering mechanism. This example USES the LRU algorithm. Let's look at the implementation of MemoryCache
 
public class MemoryCache { 
private static final String TAG = "MemoryCache"; 
//  Putting it into the cache is a synchronous operation  
// LinkedHashMap The end of the constructor 1 A parameter true On behalf of the map The elements will be arranged from the least to the most recently used, i.e LRU 
//  The advantage is that if you want to replace elements in the cache, you will be more efficient by first iterating over the least recently used elements to replace them  
private Map<String, Bitmap> cache = Collections 
.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true)); 
//  The number of bytes used by the image in the cache, initial 0 , which strictly controls the heap memory used by the cache  
private long size = 0;// current allocated size 
//  The maximum amount of memory that a cache can consume  
private long limit = 1000000;// max memory in bytes 
public MemoryCache() { 
// use 25% of available heap size 
setLimit(Runtime.getRuntime().maxMemory() / 10); 
} 
public void setLimit(long new_limit) { 
limit = new_limit; 
Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB"); 
} 
public Bitmap get(String id) { 
try { 
if (!cache.containsKey(id)) 
return null; 
return cache.get(id); 
} catch (NullPointerException ex) { 
return null; 
} 
} 
public void put(String id, Bitmap bitmap) { 
try { 
if (cache.containsKey(id)) 
size -= getSizeInBytes(cache.get(id)); 
cache.put(id, bitmap); 
size += getSizeInBytes(bitmap); 
checkSize(); 
} catch (Throwable th) { 
th.printStackTrace(); 
} 
} 
/** 
*  Strictly control the heap memory, and if it exceeds it will first replace the least recently used image cache  
* 
*/ 
private void checkSize() { 
Log.i(TAG, "cache size=" + size + " length=" + cache.size()); 
if (size > limit) { 
//  Start by walking through the least recently used elements  
Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator(); 
while (iter.hasNext()) { 
Entry<String, Bitmap> entry = iter.next(); 
size -= getSizeInBytes(entry.getValue()); 
iter.remove(); 
if (size <= limit) 
break; 
} 
Log.i(TAG, "Clean cache. New size " + cache.size()); 
} 
} 
public void clear() { 
cache.clear(); 
} 
/** 
*  Image footprint  
* 
* <A href='\"http://www.eoeandroid.com/home.php?mod=space&uid=2768922\"' target='\"_blank\"'>@Param</A> bitmap 
* 
* @return 
*/ 
long getSizeInBytes(Bitmap bitmap) { 
if (bitmap == null) 
return 0; 
return bitmap.getRowBytes() * bitmap.getHeight(); 
} 
} 

Limited memory buffer size of the heap memory images, first determine whether every time some pictures is added to the cache more than limit the size of more than if you removed from at least the image to be used and, of course, that if not in this way, in soft references are also possible, purpose is the best use of the two images that is existing in memory cache, avoid repetition make rubbish increasing GC burden, OOM overflow often because the instantaneous large increase memory and garbage collection is not in a timely manner. The difference between the two is that the cached images in LinkedHashMap will not be recycled by GC until they are removed, while the cached images in SoftReference will be recycled by GC at any time if they are saved without other references. Therefore, the cache of LinkedHashMap algorithm LRU is more conducive to the effective hit of the image. Of course, the cache removed from LinkedHashMap will be put into SoftReference, which is the level 2 cache of the memory.

Related articles: