Listview asynchronous loading performance optimization

  • 2021-01-19 22:23:18
  • OfStack

In Android, ListView is one of the controls with the highest flat rate (GridView and ListView are brothers and inherit AbsListView). The most effective way to optimize ListView is to use ViewHolder to reduce frequent queries and updates to view, cache images to speed up decoding and reduce the size of images.

About listview asynchronous loading, the Internet is actually a lot of examples, the main idea about the same, but a lot of version or bug, or performance issues need to be optimized, let next elaborated its principle in APP applications, to explore Chinese mystery listview asynchronous loading pictures can bring good user experience, but also consider application performance is one of the important indicators. There are many examples of listview asynchronous loading on the web. The main idea is the same, but many versions either have bug or have performance issues to optimize. In view of this, oneself looked for a relative ideal version on the net and undertake transformation on this basis, let below expound its principle in order to explore a mystery, with all you appreciate...

Asynchronous load picture basic idea:

1. First get the image display from the memory cache (memory buffer)
2. If not, get it from SD card (SD card buffer)
3. If not, download the picture from the network and save it to SD card, add it into memory and display it (it depends on whether to display it).

OK: OK: OK: OK


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 then see 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 for it in the file cache first  
Bitmap b = null; 
if (f != null && f.exists()){ 
b = decodeFile(f); 
} 
if (b != null){ 
return b; 
} 
//  Last from the specified url Download pictures from  
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 This image is also scaled to reduce memory consumption, and the virtual machine has a limited cache size 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 in UI In the thread  
Activity a = (Activity) photoToLoad.imageView.getContext(); 
a.runOnUiThread(bd); 
} 
} 
/** 
*  Prevent image misalignment  
* 
* @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 the interface in the 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, the open thread fetches from SD card or network. Note that fetching images from SD card is performed in the child thread, otherwise it will not be smooth enough for fast scrolling screen. This is optimization 1. At the same time, there is an busy variable in adapter, which indicates whether the listview is in the sliding state. If the listview is in the sliding state, the image will only be fetched from the memory. If not, there is no need to open the thread to fetch the image from the external storage or network. The thread in ImageLoader uses the thread pool, so as to avoid the multiple threads to create and destroy frequently, some children every time always new1 thread to execute this is very undesirable, good 1 point with the AsyncTask class, in fact, the internal is also used to thread pool. When getting a picture from the network, first save it to sd card, and then load it into memory. The advantage of this is that you can do a compression processing when loading into memory, to reduce the memory of the picture, which 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 if you are visible, and not show if you are not visible. In ImageLoader, there is an map object of imageViews, which is used to save the url set corresponding to the image of the current display area. It can be judged and processed 1 times before display.

Now let's talk about the memory buffer mechanism, this example uses the LRU algorithm, first look at the implementation of MemoryCache


public class MemoryCache { 
private static final String TAG = "MemoryCache"; 
//  When you put it into the cache it's a synchronous operation  
// LinkedHashMap The end of the constructor 1 A parameter true On behalf of the map The elements in the list will be arranged from least to most recently used, i.e LRU 
//  The advantage of this is that if you want to replace an element in the cache, you should iterate through the least recently used element first to replace it efficiently  
private Map<String, Bitmap> cache = Collections 
.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true)); 
//  The number of bytes occupied by the image in the cache, initial 0 This variable is used to strictly control the amount of heap memory consumed by the cache  
private long size = 0;// current allocated size 
//  The cache can only occupy the maximum heap of memory  
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(); 
} 
} 
/** 
*  The least recently used image cache will be replaced first if the heap memory is exceeded  
* 
*/ 
private void checkSize() { 
Log.i(TAG, "cache size=" + size + " length=" + cache.size()); 
if (size > limit) { 
//  Begin by iterating 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(); 
} 
/** 
*  Memory used by images  
* 
* <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 only difference is that the image cache in LinkedHashMap will not be retrieved by GC until it is removed, while the image cache in SoftReference will be retrieved by GC at any time if no other references are saved. So in the use of LinkedHashMap such as LRU algorithm cache is more conducive to the effective hit of the picture, of course, the effect is better when used in conjunction with 2, that is, removed from LinkedHashMap cache into SoftReference, this is the memory of the 2 cache, interested in the children's shoes out of the common 1 try.

The above is for the listview asynchronous loading performance optimization of all introduction, I hope to help you.


Related articles: