`
java_cofi
  • 浏览: 47483 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Android之ListView异步加载网络图片(优化缓存机制)

阅读更多
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码MemoryCache.java:
[java] view plaincopy
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() / 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 plaincopy
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 plaincopy
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 plaincopy
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) { 
        } 
    } 

主要流程是先从内存缓存中查找,若没有再开线程,从文件缓存中查找都没有则从指定的url中查找,并对bitmap进行处理,最后通过下面方法对UI进行更新操作。
[java] view plaincopy
a.runOnUiThread(...); 
在你的程序中的基本用法:
[java] view plaincopy
ImageLoader imageLoader=new ImageLoader(context); 
... 
imageLoader.DisplayImage(url, imageView); 
比如你的放在你的ListView的adapter的getView()方法中,当然也适用于GridView。

原文地址http://blog.csdn.net/zircon_1973/article/details/7693839
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics