In APP applications, the asynchronous loading of pictures by listview can bring a good user experience, and it is also an important indicator to consider program performance. Regarding the asynchronous loading of listview, there are actually many examples on the Internet, and the central idea is similar, but many versions may have bugs or performance issues that need to be optimized. In view of this, I found a relatively ideal version online and modified it on this basis. Let me explain its principles below to explore the mysteries and enjoy them together with you...
Post a rendering first:
Basic idea of asynchronous loading of pictures:
1. First get the image display from the memory cache (memory buffer)
2. If you cannot obtain it, get it from the SD card (SD card buffer)
3. If you can't get it, download the picture from the network and save it to the SD card and add it to the memory and display it (depending on the situation, see if it wants to be displayed)
OK, first apply the adapter code:
public class LoaderAdapter extends BaseAdapter{
private static final String TAG = "LoaderAdapter";
private boolean mBusy = false;
public void setFlagBusy(boolean busy) {
= busy;
}
private ImageLoader mImageLoader;
private int mCount;
private Context mContext;
private String[] urlArrays;
public LoaderAdapter(int count, Context context, String []url) {
= count;
= 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 = (mContext).inflate(
.list_item, null);
viewHolder = new ViewHolder();
= (TextView) convertView
.findViewById(.tv_tips);
= (ImageView) convertView
.findViewById(.iv_image);
(viewHolder);
} else {
viewHolder = (ViewHolder) ();
}
String url = "";
url = urlArrays[position % ];
(.ic_launcher);
if (!mBusy) {
(url, , false);
("--" + position
+ "--IDLE ||TOUCH_SCROLL");
} else {
(url, , true);
("--" + position + "--FLING");
}
return convertView;
}
static class ViewHolder {
TextView mTextView;
ImageView mImageView;
}
}
The key code is the DisplayImage method of ImageLoader, and then look at 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>());
// Thread pool
private ExecutorService executorService;
public ImageLoader(Context context) {
fileCache = new FileCache(context);
executorService = (5);
}
// The most important method
public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {
(imageView, url);
// Find it from the memory cache first
Bitmap bitmap = (url);
if (bitmap != null)
(bitmap);
else if (!isLoadOnlyFromCache){
// If not, start the new thread to load the picture
queuePhoto(url, imageView);
}
}
private void queuePhoto(String url, ImageView imageView) {
PhotoToLoad p = new PhotoToLoad(url, imageView);
(new PhotosLoader(p));
}
private Bitmap getBitmap(String url) {
File f = (url);
// First find out if there is any in the file cache
Bitmap b = null;
if (f != null && ()){
b = decodeFile(f);
}
if (b != null){
return b;
}
// Finally download the image from the specified url
try {
Bitmap bitmap = null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
(30000);
(30000);
(true);
InputStream is = ();
OutputStream os = new FileOutputStream(f);
CopyStream(is, os);
();
bitmap = decodeFile(f);
return bitmap;
} catch (Exception ex) {
("", "getBitmap catch Exception...\nmessage = " + ());
return null;
}
}
// Decode this image and scale it to reduce memory consumption. The virtual machine also has a limit on the cache size of each image.
private Bitmap decodeFile(File f) {
try {
// decode image size
o = new ();
= true;
(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 = , height_tmp = ;
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
o2 = new ();
= scale;
return (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) {
= photoToLoad;
}
@Override
public void run() {
if (imageViewReused(photoToLoad))
return;
Bitmap bmp = getBitmap();
(, bmp);
if (imageViewReused(photoToLoad))
return;
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
// The update operation is placed in the UI thread
Activity a = (Activity) ();
(bd);
}
}
/**
* Prevent image misalignment
*
* @param photoToLoad
* @return
*/
boolean imageViewReused(PhotoToLoad photoToLoad) {
String tag = ();
if (tag == null || !())
return true;
return false;
}
// Used to update the interface in UI 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)
(bitmap);
}
}
public void clearCache() {
();
();
}
public static void CopyStream(InputStream is, OutputStream os) {
final int buffer_size = 1024;
try {
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = (bytes, 0, buffer_size);
if (count == -1)
break;
(bytes, 0, count);
}
} catch (Exception ex) {
("", "CopyStream catch Exception...");
}
}
}
Load it from memory first. If there is no thread, start the thread to obtain it from the SD card or the network. Here, please note that the image obtained from the SD card is placed in the child thread and executed. Otherwise, it will not be smooth enough if the screen is quickly swiped. This is optimization one. At the same time, there is a busy variable in the adapter, indicating whether the listview is in a sliding state. If it is in a sliding state, only images are obtained from memory. If not, there is no need to start the thread to go out of memory or to obtain images. This is optimization two. The threads in ImageLoader use thread pools, which avoids frequent creation and destruction of too many threads. Some children always use a thread to execute each time. This is very undesirable. The better one uses the AsyncTask class, but actually uses the thread pool inside. When obtaining pictures from the network, first save them to the SD card and then load them into memory. The advantage of this is that when loading them into memory, you can perform a compression process to reduce the memory occupied by the pictures. This is Optimization Three.
The essence of the image misalignment problem comes from the fact that our listview uses a cache convertView. Suppose a scenario where a listview displays nine items on one screen. Then when the tenth item is pulled out, the item actually reuses the first item, which means that when the first item downloads the image from the network and finally displays it, the item is no longer in the current display area. The consequence of displaying at this time will be that the image may be output on the tenth item, which leads to the problem of image misalignment. Therefore, the solution is to display it if it is visible, and not display it if it is not visible. There is a map object of imageViews in ImageLoader, which is used to save the url set corresponding to the image in the current display area, and it can be judged and processed before displaying.
Let’s talk about the memory buffering mechanism below. This example uses the LRU algorithm. Let’s take a look at the implementation of MemoryCache.
public class MemoryCache {
private static final String TAG = "MemoryCache";
// It is a synchronous operation when putting in the cache
// The last parameter true of the LinkedHashMap constructor means that the elements in this map will be arranged from less to more according to the recent use times, that is, LRU
// The advantage of this is that if you want to replace the elements in the cache, first iterate out the least recently used elements to replace them to improve efficiency
private Map<String, Bitmap> cache = Collections
.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
// The bytes occupied by the image in the cache, initially 0, this variable will strictly control the heap memory occupied by the cache.
private long size = 0;// current allocated size
// The maximum heap memory that the cache can only occupy
private long limit = 1000000;// max memory in bytes
public MemoryCache() {
// use 25% of available heap size
setLimit(().maxMemory() / 10);
}
public void setLimit(long new_limit) {
limit = new_limit;
(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
}
public Bitmap get(String id) {
try {
if (!(id))
return null;
return (id);
} catch (NullPointerException ex) {
return null;
}
}
public void put(String id, Bitmap bitmap) {
try {
if ((id))
size -= getSizeInBytes((id));
(id, bitmap);
size += getSizeInBytes(bitmap);
checkSize();
} catch (Throwable th) {
();
}
}
/**
* Strictly control the heap memory. If it exceeds the image cache that is least recently used will be replaced first.
*
*/
private void checkSize() {
(TAG, "cache size=" + size + " length=" + ());
if (size > limit) {
// First iterate over the least recently used element
Iterator<Entry<String, Bitmap>> iter = ().iterator();
while (()) {
Entry<String, Bitmap> entry = ();
size -= getSizeInBytes(());
();
if (size <= limit)
break;
}
(TAG, "Clean cache. New size " + ());
}
}
public void clear() {
();
}
/**
* The memory occupied by the picture
*
* <A href='\"/?mod=space&uid=2768922\"' target='\"_blank\"'>@Param</A> bitmap
*
* @return
*/
long getSizeInBytes(Bitmap bitmap) {
if (bitmap == null)
return 0;
return () * ();
}
}
First, limit the heap memory size of the memory image buffer. Every time an image is added to the cache, determine whether it exceeds the limit size. If it exceeds the limit, take out the least used image from it and remove it. Of course, if this method is not adopted here, it is feasible to use soft references. Both are to maximize the use of the image cache already exists in the memory to avoid repeated production of garbage to increase the burden of GC. OOM overflow is often caused by the instantaneous increase in memory and the garbage collection is not timely. However, the difference between the two is that the image cache in LinkedHashMap will not be recycled by GC before it is removed, while the image cache in SoftReference will be recycled by GC at any time when it is saved without other references. Therefore, using LinkedHashMap, an LRU algorithm cache is more conducive to the effective hit of the picture. Of course, the effect is better if the two are used together, that is, the cache removed from LinkedHashMap is placed in the SoftReference. This is the second-level cache of memory. If you are interested, you can try it out.
Post a rendering first:
Basic idea of asynchronous loading of pictures:
1. First get the image display from the memory cache (memory buffer)
2. If you cannot obtain it, get it from the SD card (SD card buffer)
3. If you can't get it, download the picture from the network and save it to the SD card and add it to the memory and display it (depending on the situation, see if it wants to be displayed)
OK, first apply the adapter code:
Copy the codeThe code is as follows:
public class LoaderAdapter extends BaseAdapter{
private static final String TAG = "LoaderAdapter";
private boolean mBusy = false;
public void setFlagBusy(boolean busy) {
= busy;
}
private ImageLoader mImageLoader;
private int mCount;
private Context mContext;
private String[] urlArrays;
public LoaderAdapter(int count, Context context, String []url) {
= count;
= 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 = (mContext).inflate(
.list_item, null);
viewHolder = new ViewHolder();
= (TextView) convertView
.findViewById(.tv_tips);
= (ImageView) convertView
.findViewById(.iv_image);
(viewHolder);
} else {
viewHolder = (ViewHolder) ();
}
String url = "";
url = urlArrays[position % ];
(.ic_launcher);
if (!mBusy) {
(url, , false);
("--" + position
+ "--IDLE ||TOUCH_SCROLL");
} else {
(url, , true);
("--" + position + "--FLING");
}
return convertView;
}
static class ViewHolder {
TextView mTextView;
ImageView mImageView;
}
}
The key code is the DisplayImage method of ImageLoader, and then look at the implementation of ImageLoader
Copy the codeThe code is as follows:
public class ImageLoader {
private MemoryCache memoryCache = new MemoryCache();
private AbstractFileCache fileCache;
private Map<ImageView, String> imageViews = Collections
.synchronizedMap(new WeakHashMap<ImageView, String>());
// Thread pool
private ExecutorService executorService;
public ImageLoader(Context context) {
fileCache = new FileCache(context);
executorService = (5);
}
// The most important method
public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {
(imageView, url);
// Find it from the memory cache first
Bitmap bitmap = (url);
if (bitmap != null)
(bitmap);
else if (!isLoadOnlyFromCache){
// If not, start the new thread to load the picture
queuePhoto(url, imageView);
}
}
private void queuePhoto(String url, ImageView imageView) {
PhotoToLoad p = new PhotoToLoad(url, imageView);
(new PhotosLoader(p));
}
private Bitmap getBitmap(String url) {
File f = (url);
// First find out if there is any in the file cache
Bitmap b = null;
if (f != null && ()){
b = decodeFile(f);
}
if (b != null){
return b;
}
// Finally download the image from the specified url
try {
Bitmap bitmap = null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
(30000);
(30000);
(true);
InputStream is = ();
OutputStream os = new FileOutputStream(f);
CopyStream(is, os);
();
bitmap = decodeFile(f);
return bitmap;
} catch (Exception ex) {
("", "getBitmap catch Exception...\nmessage = " + ());
return null;
}
}
// Decode this image and scale it to reduce memory consumption. The virtual machine also has a limit on the cache size of each image.
private Bitmap decodeFile(File f) {
try {
// decode image size
o = new ();
= true;
(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 = , height_tmp = ;
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
o2 = new ();
= scale;
return (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) {
= photoToLoad;
}
@Override
public void run() {
if (imageViewReused(photoToLoad))
return;
Bitmap bmp = getBitmap();
(, bmp);
if (imageViewReused(photoToLoad))
return;
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
// The update operation is placed in the UI thread
Activity a = (Activity) ();
(bd);
}
}
/**
* Prevent image misalignment
*
* @param photoToLoad
* @return
*/
boolean imageViewReused(PhotoToLoad photoToLoad) {
String tag = ();
if (tag == null || !())
return true;
return false;
}
// Used to update the interface in UI 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)
(bitmap);
}
}
public void clearCache() {
();
();
}
public static void CopyStream(InputStream is, OutputStream os) {
final int buffer_size = 1024;
try {
byte[] bytes = new byte[buffer_size];
for (;;) {
int count = (bytes, 0, buffer_size);
if (count == -1)
break;
(bytes, 0, count);
}
} catch (Exception ex) {
("", "CopyStream catch Exception...");
}
}
}
Load it from memory first. If there is no thread, start the thread to obtain it from the SD card or the network. Here, please note that the image obtained from the SD card is placed in the child thread and executed. Otherwise, it will not be smooth enough if the screen is quickly swiped. This is optimization one. At the same time, there is a busy variable in the adapter, indicating whether the listview is in a sliding state. If it is in a sliding state, only images are obtained from memory. If not, there is no need to start the thread to go out of memory or to obtain images. This is optimization two. The threads in ImageLoader use thread pools, which avoids frequent creation and destruction of too many threads. Some children always use a thread to execute each time. This is very undesirable. The better one uses the AsyncTask class, but actually uses the thread pool inside. When obtaining pictures from the network, first save them to the SD card and then load them into memory. The advantage of this is that when loading them into memory, you can perform a compression process to reduce the memory occupied by the pictures. This is Optimization Three.
The essence of the image misalignment problem comes from the fact that our listview uses a cache convertView. Suppose a scenario where a listview displays nine items on one screen. Then when the tenth item is pulled out, the item actually reuses the first item, which means that when the first item downloads the image from the network and finally displays it, the item is no longer in the current display area. The consequence of displaying at this time will be that the image may be output on the tenth item, which leads to the problem of image misalignment. Therefore, the solution is to display it if it is visible, and not display it if it is not visible. There is a map object of imageViews in ImageLoader, which is used to save the url set corresponding to the image in the current display area, and it can be judged and processed before displaying.
Let’s talk about the memory buffering mechanism below. This example uses the LRU algorithm. Let’s take a look at the implementation of MemoryCache.
Copy the codeThe code is as follows:
public class MemoryCache {
private static final String TAG = "MemoryCache";
// It is a synchronous operation when putting in the cache
// The last parameter true of the LinkedHashMap constructor means that the elements in this map will be arranged from less to more according to the recent use times, that is, LRU
// The advantage of this is that if you want to replace the elements in the cache, first iterate out the least recently used elements to replace them to improve efficiency
private Map<String, Bitmap> cache = Collections
.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
// The bytes occupied by the image in the cache, initially 0, this variable will strictly control the heap memory occupied by the cache.
private long size = 0;// current allocated size
// The maximum heap memory that the cache can only occupy
private long limit = 1000000;// max memory in bytes
public MemoryCache() {
// use 25% of available heap size
setLimit(().maxMemory() / 10);
}
public void setLimit(long new_limit) {
limit = new_limit;
(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
}
public Bitmap get(String id) {
try {
if (!(id))
return null;
return (id);
} catch (NullPointerException ex) {
return null;
}
}
public void put(String id, Bitmap bitmap) {
try {
if ((id))
size -= getSizeInBytes((id));
(id, bitmap);
size += getSizeInBytes(bitmap);
checkSize();
} catch (Throwable th) {
();
}
}
/**
* Strictly control the heap memory. If it exceeds the image cache that is least recently used will be replaced first.
*
*/
private void checkSize() {
(TAG, "cache size=" + size + " length=" + ());
if (size > limit) {
// First iterate over the least recently used element
Iterator<Entry<String, Bitmap>> iter = ().iterator();
while (()) {
Entry<String, Bitmap> entry = ();
size -= getSizeInBytes(());
();
if (size <= limit)
break;
}
(TAG, "Clean cache. New size " + ());
}
}
public void clear() {
();
}
/**
* The memory occupied by the picture
*
* <A href='\"/?mod=space&uid=2768922\"' target='\"_blank\"'>@Param</A> bitmap
*
* @return
*/
long getSizeInBytes(Bitmap bitmap) {
if (bitmap == null)
return 0;
return () * ();
}
}
First, limit the heap memory size of the memory image buffer. Every time an image is added to the cache, determine whether it exceeds the limit size. If it exceeds the limit, take out the least used image from it and remove it. Of course, if this method is not adopted here, it is feasible to use soft references. Both are to maximize the use of the image cache already exists in the memory to avoid repeated production of garbage to increase the burden of GC. OOM overflow is often caused by the instantaneous increase in memory and the garbage collection is not timely. However, the difference between the two is that the image cache in LinkedHashMap will not be recycled by GC before it is removed, while the image cache in SoftReference will be recycled by GC at any time when it is saved without other references. Therefore, using LinkedHashMap, an LRU algorithm cache is more conducive to the effective hit of the picture. Of course, the effect is better if the two are used together, that is, the cache removed from LinkedHashMap is placed in the SoftReference. This is the second-level cache of memory. If you are interested, you can try it out.