`
java_cofi
  • 浏览: 48825 次
  • 性别: 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
分享到:
评论

相关推荐

    Android实现ListView异步加载图片

    "Android实现ListView异步加载图片" Android 实现 ListView 异步加载图片是一种常见的技术,旨在提高应用程序的性能和用户体验。本文将详细介绍 Android 中实现 ListView 异步加载图片的方法,并对相关的技术概念...

    listview 异步加载网络图片

    10. **总结**:在Android的ListView中实现异步加载网络图片,需要结合异步处理框架、选择合适的图片库、优化缓存策略、合理管理内存,并对ListView进行优化。通过这些手段,可以显著提升应用的性能和用户体验。

    Android Listview异步加载图片

    综上所述,Android ListView中异步加载图片是优化用户体验的关键。通过选择合适的异步加载策略和处理图片错位问题,我们可以确保ListView流畅运行,提供优质的用户体验。同时,利用第三方库可以大大简化代码,提高...

    ListView异步加载图片

    本篇文章将详细介绍如何通过软引用缓存图片,实现高效、流畅的异步加载机制。 一、异步加载原理 异步加载的基本思想是将耗时的操作(如网络请求和图片解码)放在后台线程执行,以避免阻塞主线程。在ListView中,当...

    ListView异步加载图片三级缓存

    总结起来,"ListView异步加载图片三级缓存"是一个重要的Android性能优化技巧,它结合了异步处理、内存管理、磁盘操作以及网络通信,为用户提供流畅、高效的图片浏览体验。在LazyLoaderDemo这样的示例代码中,我们...

    ListView的异步加载图片并缓存

    因此,"ListView的异步加载图片并缓存"是解决这一问题的关键技术。 异步加载图片的主要目的是将耗时的操作(如网络请求、图片解码)放在后台线程,以避免阻塞UI线程,提高用户体验。以下是一些关键知识点: 1. **...

    Android中ListView全面完美的网络图片的异步加载

    综上所述,实现Android中ListView全面完美的网络图片异步加载需要结合异步加载库(如Picasso或Glide)、LruCache缓存策略以及动态加载技术。这些方法的运用可以显著提升应用性能,为用户提供流畅的滚动体验,同时...

    android ListView异步加载图片和优化

    本篇将深入探讨如何在Android ListView中实现异步加载图片以及相关的优化策略。 1. **异步加载图片**:在ListView中,由于滚动频繁,直接同步加载网络图片会阻塞主线程,导致界面卡顿。为了解决这个问题,可以使用...

    Android开发之listview优化+图片异步加载缓存+避免图片显示闪烁

    综上所述,通过ListView的优化、图片的异步加载和缓存策略,以及避免图片闪烁的方法,可以显著提升Android应用的性能和用户体验。实际开发中,结合具体情况灵活运用这些技术,能有效地解决类似问题。

    Android ListView优化 异步加载图片

    你可以通过分析这些代码,更好地理解ListView异步加载图片的实现过程。 通过以上介绍,你应该对Android ListView的优化有了更深入的理解,特别是异步加载图片和Json解析这两个关键环节。实践中,结合各种优化策略,...

    Android初学者之Listview异步加载网络图片并动

    对于初学者来说,理解并实现ListView的异步加载网络图片以及动态更新是非常重要的技能。这个主题通常涵盖以下几个关键知识点: 1. **ListView的基础使用**:首先,你需要了解ListView的基本结构,包括Adapter、...

    android中ListView异步加载图片错位

    为了解决“android中ListView异步加载图片错位”这一问题,开发者可以采取以下几种策略: 1. 使用 ViewHolder 设计模式:ViewHolder模式能提高ListView的滚动性能,通过缓存convertView中的视图对象,避免了频繁的...

    ListView异步加载网络图片

    下面将详细讲解如何在ListView中实现异步加载网络图片。 首先,理解异步加载的基本概念。异步加载是指在后台线程执行耗时操作,如下载和解码图片,而不阻塞主线程,这样可以保证用户界面的流畅性。在Android中,...

    listview异步加载网络图片

    总的来说,“listview异步加载图片”是一项关键的性能优化技术,它结合了异步编程和内存管理的策略,确保了ListView的流畅滚动和系统的稳定运行。通过理解并应用这些知识,开发者可以创建出更高效、更用户友好的...

    Android ListView 异步加载图片

    本文将详细介绍如何使用AsyncTask和WeakReference实现Android ListView中图片的异步加载。 首先,AsyncTask是Android提供的一种轻量级的多线程解决方案,适用于执行后台任务并更新UI。在ListView中,我们可以在...

    ListView异步加载网络图片之一

    在“ListView异步加载网络图片之一”这篇博文中,作者可能讨论了以下几点: 1. **异步加载原理**:使用AsyncTask、Handler、Thread、Runnable或者第三方库如Volley、Picasso、Glide等来在后台线程下载图片,下载...

Global site tag (gtag.js) - Google Analytics