listview优化(中)

    xiaoxiao2026-02-08  1

    1,对Imageview使用setTag()方法来解决图片错位问题,这个Tag中设置的是图片的url,然后在加载的时候取得这个url和要加载那position中的url对比,如果不相同就加载,相同就是复用以前的就不加载了

    2,对于要加载的图片资源,先在内存缓存中找(原始的方法是使用SoftRefrence,最新的方法是使用android提供的Lrucache),如果找不到,则在本地缓存(可以使用DiskLrucache类)中找(也就是读取原先下载过的本地图片),还找不到,就开启异步线程去下载图片,下载以后,保存在本地,内存缓存也保留一份引用

    3,在为imagview装载图片时,先测量需要的图片大小,按比例缩放

    4,使用一个Map保存异步线程的引用,key->value为url->AsyncTask,这样可以避免已经开启了线程去加载图片,但是还没有加载完时,又重复开启线程去加载图片的情况

    5,在快速滑动的时候不加载图片,取消所有图片加载线程,一旦停下来,继续可见图片的加载线程

    下面都是我摘取的网上的一些例子,我分别介绍它们来说明上述的优化思路

    第一个例子:

    [java]  view plain copy public class MemoryCache {          private static final String TAG = "MemoryCache";       // 放入缓存时是个同步操作       // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU       // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率       private Map<String, Bitmap> cache = Collections               .synchronizedMap(new LinkedHashMap<String, Bitmap>(101.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() / 4);       }          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();       }          /**       * 图片占用的内存       *        * @param bitmap       * @return       */       long getSizeInBytes(Bitmap bitmap) {           if (bitmap == null)               return 0;           return bitmap.getRowBytes() * bitmap.getHeight();       }   }   也可以使用SoftReference,代码会简单很多,但是我推荐上面的方法。 [java]  view plain copy public class MemoryCache {              private Map<String, SoftReference<Bitmap>> cache = Collections               .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());          public Bitmap get(String id) {           if (!cache.containsKey(id))               return null;           SoftReference<Bitmap> ref = cache.get(id);           return ref.get();       }          public void put(String id, Bitmap bitmap) {           cache.put(id, new SoftReference<Bitmap>(bitmap));       }          public void clear() {           cache.clear();       }      }   下面是文件缓存类的代码FileCache.java: [java]  view plain copy public class FileCache {          private File cacheDir;          public FileCache(Context context) {           // 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片           // 没有SD卡就放在系统的缓存目录中           if (android.os.Environment.getExternalStorageState().equals(                   android.os.Environment.MEDIA_MOUNTED))               cacheDir = new File(                       android.os.Environment.getExternalStorageDirectory(),                       "LazyList");           else               cacheDir = context.getCacheDir();           if (!cacheDir.exists())               cacheDir.mkdirs();       }          public File getFile(String url) {           // 将url的hashCode作为缓存的文件名           String filename = String.valueOf(url.hashCode());           // Another possible solution           // String filename = URLEncoder.encode(url);           File f = new File(cacheDir, filename);           return f;          }          public void clear() {           File[] files = cacheDir.listFiles();           if (files == null)               return;           for (File f : files)               f.delete();       }      }  

    最后最重要的加载图片的类,ImageLoader.java:

    [java]  view plain copy public class ImageLoader {          MemoryCache memoryCache = new MemoryCache();       FileCache fileCache;       private Map<ImageView, String> imageViews = Collections               .synchronizedMap(new WeakHashMap<ImageView, String>());       // 线程池       ExecutorService executorService;          public ImageLoader(Context context) {           fileCache = new FileCache(context);           executorService = Executors.newFixedThreadPool(5);       }          // 当进入listview时默认的图片,可换成你自己的默认图片       final int stub_id = R.drawable.stub;          // 最主要的方法       public void DisplayImage(String url, ImageView imageView) {           imageViews.put(imageView, url);           // 先从内存缓存中查找              Bitmap bitmap = memoryCache.get(url);           if (bitmap != null)               imageView.setImageBitmap(bitmap);           else {               // 若没有的话则开启新线程加载图片               queuePhoto(url, imageView);               imageView.setImageResource(stub_id);           }       }          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 = 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) {               ex.printStackTrace();               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 = 70;               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);               else                   photoToLoad.imageView.setImageResource(stub_id);           }       }          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) {           }       }   }  

    上面代码的思路是这样的,首先是一个MemoryCache类,用来缓存图片应用到内存。这个类包含一个Collectiosn.synchronizedMap(new LinkedHashMap<String,Bitmap>(10,1.5f,true))对象,这个对象就是用来保存url和对应的bitmap的,也就是缓存,最后一个参数设置为true的原因,是代表这个map里的元素将按照最近使用次数由少到多排列,即LRU。这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率 。

    另外设置一个缓存的最大值limit,和一个初始值size=0。每次添加图片缓存,Size就增加相应大小,如果增加以后大小超过limit,就遍历LinkedHashMap清楚使用次数最少的缓存,同时减小size值,直到size<limit。

    作者还举了一个使用SoftReference的例子,这样做的好处是android会自动替我们回收适当的bitmap缓存。

    接下来是文件缓存,如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片,没有SD卡就放在系统的缓存目录中,将url的hashCode作为缓存的文件名。这个类只是根据url名创建并返回了一个File类,没有真正的缓存图片,图片缓存在ImageLoader类中,不过这个类要获取FileCache返回的File来做FileOutputStream的目的地.

    最后是负责的ImageLoader,这个类有一个线程池,用于管理下载线程。另外有一个WeakHashMap<ImageView, String>用于保存imageview引用和记录Tag,用于图片更新。它先检查缓存,没有则开启一个线程去下载,下载以后图片保存到缓存(内存,文件),然后缩放图像比例,返回一个合适大小的bitmap,最后开启一个线程去跟新UI(方式是imagview.getContext()获取对应的context,然后context调用runOnUIThread()方法)。

    另外,在下载线程开启前,图片下载完成后,跟新UI前,都通过WeakHashMap<ImageView, String>获取下载图片的Tag与对应要设置图片imageview的tag比较,防止图片错位。

    上述代码完成了基本的优化思路,甚至使用了一个自己定义的缓存类MemoryCache,使管理变得更加清晰,同时有文件缓存,也通过imagview->url的方式避免了图片错位,还开启了异步线程下载图片,但是又开启了一个UI线程去跟新UI。

    缺点是开启了UI线程去更新UI,浪费了资源,其实这个可以使用定义一个回调接口实现。另外也没有考虑到重复开启下载线程的问题。

    第二个例子:

    先贴上主方法的代码:

    [java]  view plain copy package cn.wangmeng.test;      import java.io.IOException;   import java.io.InputStream;   import java.lang.ref.SoftReference;   import java.net.MalformedURLException;   import java.net.URL;   import java.util.HashMap;      import android.graphics.drawable.Drawable;   import android.os.Handler;   import android.os.Message;      public class AsyncImageLoader {           private HashMap<String, SoftReference<Drawable>> imageCache;                     public AsyncImageLoader() {                imageCache = new HashMap<String, SoftReference<Drawable>>();            }                     public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {                if (imageCache.containsKey(imageUrl)) {                    SoftReference<Drawable> softReference = imageCache.get(imageUrl);                    Drawable drawable = softReference.get();                    if (drawable != null) {                        return drawable;                    }                }                final Handler handler = new Handler() {                    public void handleMessage(Message message) {                        imageCallback.imageLoaded((Drawable) message.obj, imageUrl);                    }                };                new Thread() {                    @Override                    public void run() {                        Drawable drawable = loadImageFromUrl(imageUrl);                        imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));                        Message message = handler.obtainMessage(0, drawable);                        handler.sendMessage(message);                    }                }.start();                return null;            }                    public static Drawable loadImageFromUrl(String url) {               URL m;               InputStream i = null;               try {                   m = new URL(url);                   i = (InputStream) m.getContent();               } catch (MalformedURLException e1) {                   e1.printStackTrace();               } catch (IOException e) {                   e.printStackTrace();               }               Drawable d = Drawable.createFromStream(i, "src");               return d;           }                     public interface ImageCallback {                public void imageLoaded(Drawable imageDrawable, String imageUrl);            }      }   以上代码是实现异步获取图片的主方法,SoftReference是软引用,是为了更好的为了系统回收变量,重复的URL直接返回已有的资源,实现回调函数,让数据成功后,更新到UI线程。 几个辅助类文件: [java]  view plain copy package cn.wangmeng.test;      public class ImageAndText {           private String imageUrl;           private String text;              public ImageAndText(String imageUrl, String text) {               this.imageUrl = imageUrl;               this.text = text;           }           public String getImageUrl() {               return imageUrl;           }           public String getText() {               return text;           }   }   [java]  view plain copy package cn.wangmeng.test;      import android.view.View;   import android.widget.ImageView;   import android.widget.TextView;      public class ViewCache {              private View baseView;           private TextView textView;           private ImageView imageView;              public ViewCache(View baseView) {               this.baseView = baseView;           }              public TextView getTextView() {               if (textView == null) {                   textView = (TextView) baseView.findViewById(R.id.text);               }               return textView;           }              public ImageView getImageView() {               if (imageView == null) {                   imageView = (ImageView) baseView.findViewById(R.id.image);               }               return imageView;           }      }  

    ViewCache是辅助获取adapter的子元素布局

    [java]  view plain copy package cn.wangmeng.test;      import java.util.List;      import cn.wangmeng.test.AsyncImageLoader.ImageCallback;      import android.app.Activity;   import android.graphics.drawable.Drawable;   import android.view.LayoutInflater;   import android.view.View;   import android.view.ViewGroup;   import android.widget.ArrayAdapter;   import android.widget.ImageView;   import android.widget.ListView;   import android.widget.TextView;      public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {              private ListView listView;           private AsyncImageLoader asyncImageLoader;              public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts, ListView listView) {               super(activity, 0, imageAndTexts);               this.listView = listView;               asyncImageLoader = new AsyncImageLoader();           }              public View getView(int position, View convertView, ViewGroup parent) {               Activity activity = (Activity) getContext();                  // Inflate the views from XML               View rowView = convertView;               ViewCache viewCache;               if (rowView == null) {                   LayoutInflater inflater = activity.getLayoutInflater();                   rowView = inflater.inflate(R.layout.image_and_text_row, null);                   viewCache = new ViewCache(rowView);                   rowView.setTag(viewCache);               } else {                   viewCache = (ViewCache) rowView.getTag();               }               ImageAndText imageAndText = getItem(position);                  // Load the image and set it on the ImageView               String imageUrl = imageAndText.getImageUrl();               ImageView imageView = viewCache.getImageView();               imageView.setTag(imageUrl);               Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() {                   public void imageLoaded(Drawable imageDrawable, String imageUrl) {                       ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);                       if (imageViewByTag != null) {                           imageViewByTag.setImageDrawable(imageDrawable);                       }                   }               });               if (cachedImage == null) {                   imageView.setImageResource(R.drawable.default_image);               }else{                   imageView.setImageDrawable(cachedImage);               }               // Set the text on the TextView               TextView textView = viewCache.getTextView();               textView.setText(imageAndText.getText());                  return rowView;           }      }  

    上述代码的思路是这样的:AsyncImageLoader类里面,使用了一个HashMap<String, SoftReference<Drawable>>用来缓存,然后有一个异步下载线程,还有一个方法内部的handler,线程下载完成后,会发消息给handler,然后handler调用回调接口imageCallback的imageLoaded()方法,这个方法是在adapter里面实现的,所以也就是在主线程跟新UI了。

    而ViewCache类的作用其实就是ViewHolder,ImageAndText是一个bean类。

    在adapter中,使用mageView.setTag(imageUrl)为imageview提供一个唯一标识Url,所以先图片下载完成以后,imageCallback的imageLoaded()方法中,就可以调用listview的findViewWithTag(imageUrl)来找到对应的imageview,从而不用担心错误的问题,这个方法比较巧妙。

    缺点是没有实现文件缓存,另外也没有解决出现多个线程下载同一张图片的问题。

    相关资源:python入门教程(PDF版)
    最新回复(0)