0
点赞
收藏
分享

微信扫一扫

Listview异步加载图片之优化篇


在 APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是考量程序性能的一个重要指标。关于listview的异步加载,网上 其实很多示例了,中心思想都差不多,不过很多版本或是有bug,或是有性能问题有待优化。有鉴于此,本人在网上找了个相对理想的版本并在此基础上进行改 造,下面就让在下阐述其原理以探索个中奥秘

 

贴张效果图先:

         异步加载图片基本思想:

<iframe id="aswift_0" style="box-sizing: border-box; border-width: 0px; border-style: initial; margin: 0px; padding: 0px; left: 0px; position: absolute; top: 0px;" name="aswift_0" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="728" height="90"></iframe>

1.      先从内存缓存中获取图片显示(内存缓冲)

2.      获取不到的话从SD卡里获取(SD卡缓冲)

3.      都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)

 

OK,先上adapter的代码:

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;         


                    }         


          }




关键代码是ImageLoader的DisplayImage方法,再看ImageLoader的实现

public class ImageLoader {         


                    


                    private MemoryCache memoryCache =           new          MemoryCache();         


                    private AbstractFileCache fileCache;         


                    private Map<ImageView, String> imageViews = Collections         


                    .synchronizedMap(          new          WeakHashMap<ImageView, String>());         


                    // 线程池         


                    private ExecutorService executorService;         


                    


                    public ImageLoader(Context context) {         


                    fileCache =           new          FileCache(context);         


                    executorService = Executors.newFixedThreadPool(5);         


                    }         


                    


                    // 最主要的方法         


                    public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {         


                    imageViews.put(imageView, url);         


                    // 先从内存缓存中查找         


                    


                    Bitmap bitmap = memoryCache.get(url);         


                    if          (bitmap !=           null          )         


                    imageView.setImageBitmap(bitmap);         


                    else          if          (!isLoadOnlyFromCache){         


                    


                    // 若没有的话则开启新线程加载图片         


                    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);         


                    


                    // 先从文件缓存中查找是否有         


                    Bitmap b =           null          ;         


                    if          (f !=           null          && f.exists()){         


                    b = decodeFile(f);         


                    }         


                    if          (b !=           null          ){         


                    return          b;         


                    }         


                    // 最后从指定的url中下载图片         


                    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这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的         


                    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);         


                    // 更新的操作放在UI线程中         


                    Activity a = (Activity) photoToLoad.imageView.getContext();         


                    a.runOnUiThread(bd);         


                    }         


                    }         


                    


                    /**         


                    * 防止图片错位         


                    *          


                    * @param photoToLoad         


                    * @return         


                    */         


                    boolean imageViewReused(PhotoToLoad photoToLoad) {         


                    String tag = imageViews.get(photoToLoad.imageView);         


                    if          (tag ==           null          || !tag.equals(photoToLoad.url))         


                    return          true          ;         


                    return          false          ;         


                    }         


                    


                    // 用于在UI线程中更新界面         


                    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..."          );         


                    }         


                    }         


          }



先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从 SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅,这是优化一。于此同时,在adapter里有个busy变量,表示listview是 否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片,这是优化二。ImageLoader里的线程使用了 线程池,从而避免了过多线程频繁创建和销毁,有的童鞋每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用 到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存,这是优 化三。

而 图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出 第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候其实该item已经不 在当前显示区域内了,此时显示的后果将是在可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。在 ImageLoader里有个imageViews的map对象,就是用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。

下面再说下内存缓冲机制,本例采用的是LRU算法,先看看MemoryCache的实现

public class MemoryCache {         


                    


                    private static final String TAG =           "MemoryCache"          ;         


                    // 放入缓存时是个同步操作         


                    // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU         


                    // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率         


                    private Map<String, Bitmap> cache = Collections         


                    .synchronizedMap(          new          LinkedHashMap<String, Bitmap>(10, 1.5f,           true          ));         


                    // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存         


                    private long size = 0;          // current allocated size         


                    // 缓存只能占用的最大堆内存         


                    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();         


                    }         


                    }         


                    


                    /**         


                    * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存         


                    *          


                    */         


                    private void checkSize() {         


                    Log.i(TAG,           "cache size="          + size +           " length="          + cache.size());         


                    if          (size > limit) {         


                    // 先遍历最近最少使用的元素         


                    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();         


                    }         


                    


                    /**         


                    * 图片占用的内存         


                    *          


                    * <a href="\"http://www.jcodecraeer.com/\"" target="\"_blank\"">@Param</a> bitmap         


                    *          


                    * @return         


                    */         


                    long getSizeInBytes(Bitmap bitmap) {         


                    if          (bitmap ==           null          )         


                    return          0;         


                    return          bitmap.getRowBytes() * bitmap.getHeight();         


                    }         


          }



首 先限制内存图片缓冲的堆内存大小,每次有图片往缓存里加时判断是否超过限制大小,超过的话就从中取出最少使用的图片并将其移除,当然这里如果不采用这种方 式,换做软引用也是可行的,二者目的皆是最大程度的利用已存在于内存中的图片缓存,避免重复制造垃圾增加GC负担,OOM溢出往往皆因内存瞬时大量增加而 垃圾回收不及时造成的。只不过二者区别在于LinkedHashMap里的图片缓存在没有移除出去之前是不会被GC回收的,而SoftReference 里的图片缓存在没有其他引用保存时随时都会被GC回收。所以在使用LinkedHashMap这种LRU算法缓存更有利于图片的有效命中,当然二者配合使 用的话效果更佳,即从LinkedHashMap里移除出的缓存放到SoftReference里,这就是内存的二级缓存,有兴趣的童鞋不凡一试。

举报

相关推荐

0 条评论