Android之图片缓存管理

本文探讨了Android应用中图片缓存的管理,特别是手Q使用的策略。内容涉及Java的强引用和弱引用,包括软引用、弱引用和虚引用的区别。文章指出,为了优化缓存,通常采用强引用和弱引用双重缓存,强引用用于存储常用数据,不常用的数据则放入弱引用缓存,以避免内存不足时被立即回收。
摘要由CSDN通过智能技术生成
如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。
因此,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;

3、从网络下载图片,并更新到内存缓存和文件缓存。

后两个步骤纯碎属于业务逻辑,暂且不表,这里来看一下手Q使用的图片缓存管理策略。
说到缓存管理,首先谈一下java中的强引用和弱引用

  • 强引用:最普遍的引用,若一个对象具有强引用,那么GC绝不会回收它。如A a = new A()
  • 弱引用: 弱引用又分为以下三类:
    • 软引用(SoftReference): 这类引用只有当内存空间不足GC才会回收它
    • 弱引用(WeakReference): 这类引用拥有更短的生命周期,GC扫描过程中一旦发现了此类引用,不管当前内存是否足够,立即回收
    • 虚引用(PhantomRefence): 这类引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,则任何时刻都可能被回收

下面来看看这样一个图片缓存类,为了更大限度使用缓存,它使用了强引用缓存(强引用)和弱引用缓存(弱引用)双重缓存,强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入弱引用缓存。**

ImageCache.java

public class ImageCache {
    private static final String TAG = "ImageCache";

    //CustomLruCache是一个继承了LruCache的继承类,它代表强引用缓存,它的缓存大小一般由业务方提供
    private CustomLruCache<String, Drawable> mMemoryCache;// Default memory cache size

    //这里设置的是弱引用缓存以及它所占据的空间大小
    private static final int DEFAULT_MEM_CACHE_SIZE = 5; // 5MB
    private final HashMap<String, WeakReference<Drawable>> mRefCache = new HashMap<String, WeakReference<Drawable>>();

    public ImageCache(int memSize) {
        memSize = Math.max(memSize, DEFAULT_MEM_CACHE_SIZE);
        QLog.d(TAG, "Memory cache size = " + memSize + "MB");
        mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {

            //这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
            @Override
            protected int sizeOf(String key, Drawable drawable) {
                if (drawable instanceof BitmapDrawable) {
                    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
                    if (bitmap != null) {
                        //若是bitmap位图则直接计算它的大小
                        return bitmap.getRowBytes() * bitmap.getHeight();
                    }
                    return 0;
                } else if (drawable instanceof AnimationDrawable) {
                    //若是逐帧动画,则首先获取它所有的帧数,再计算总共的大小
                    AnimationDrawable anim = (AnimationDrawable) drawable;
                    int count = anim.getNumberOfFrames();
                    int memSize = 0;
                    for (int i = 0; i < count; i++) {
                        Drawable dr = anim.getFrame(i);
                        if (dr instanceof BitmapDrawable) {
                            Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
                            if (bitmap != null) {
                                memSize += bitmap.getRowBytes() * bitmap.getHeight();
                            }
                        }
                    }
                    return memSize;
                }
                return 0;
            }
        };
    }

    //从缓存中获取图片
    public Drawable getImageFromMemCache(String key) {
        Drawable memDrawable = null;
        if (mMemoryCache != null) {
            //首先从强引用缓存中获取图片,若找到的话,把元素移动到CustomLruCache的最后面,从而保证它在LRU算法中最后被删除?
            //疑问,其实LinkedHashMap本身就存在LRU的算法机制,因此,get的时候,会自动移入到队列尾部
            memDrawable = mMemoryCache.remove(key);
            if (memDrawable != null) {
                memDrawable = memDrawable.getConstantState().newDrawable();
                mMemoryCache.put(key, memDrawable);
                return memDrawable;
            }
        }

        //强引用缓存中没有找到,开始在弱引用缓存中查找
        WeakReference<Drawable> ref = mRefCache.get(key);
        if (ref != null) {
            //若找到的话,这里是否添加一步,将其从弱引用缓存移入强引用缓存中比较好
            memDrawable = ref.get();
            if (memDrawable == null) {
                mRefCache.remove(key);
            }
        }
        return memDrawable;
    }

    //添加图片到缓存,这里不理解为什么要向强引用缓存和弱引用缓存都要添加一份
    public void addImageToCache(String data, Drawable drawable) {
        // Add to memory cache
        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
            mMemoryCache.put(data, drawable);
            mRefCache.put(data, new WeakReference<Drawable>(drawable));
        }
    }

    //从缓存中删除资源
    public void removeImageFromCache(String data) {
        if (mRefCache != null) {
            mRefCache.remove(data);
        }
        if (mMemoryCache != null) {
            mMemoryCache.remove(data);
        }
    }

    public Drawable getImageFromDiskCache(String pathName) {
        // TODO 暂不支持disk cache
        return null;
    }

    public void clearCaches() {
        // mDiskCache.clearCache();
        mMemoryCache.evictAll();
        mRefCache.clear();
    }
}   

整个缓存策略是使用弱引用缓存和强引用缓存配合使用,并结合LRUCache,在尽可能地利用缓存的基础上,也大大提高了缓存命中率。我个人觉得这个类有改进的地方,比如,当LRUCache在移除元素的时候,默认是直接删除掉。这里更好的方式是重写LRUCache的entryRemoved方法,使得强引用缓存满的时候,会根据LRU算法将最近最久没有被使用的图片自动移入弱引用缓存,如下:

 mMemoryCache = new CustomLruCache<String, Drawable>(memSize * 1024 * 1024) {

    //这里重写了LruCache的sizeOf方法,来计算每个图片资源所占用内存的字节数
      @Override
      protected int sizeOf(String key, Drawable drawable) {
                .........
      }

      当强引用缓存满时,会自动调用这个方法,这时候,将移除的元素添加到弱引用缓存中
      @Override
      protected void entryRemoved(boolean evicted, String key, Drawable oldDrawable, Drawable newDrawable) {
            if (oldDawable != null) {
                mRefCache.put(data, new WeakReference<Drawable>(oldDawable));
            }
      }
  };    

接下来看内存缓存类:ImageMemoryCache
public class ImageMemoryCache {
   
    private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量
    private static LruCache mLruCache;  //硬引用缓存
    private static LinkedHashMap> mSoftCache;  //软引用缓存
                                                                                          
    public ImageMemoryCache(Context context) {
        int memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4
        mLruCache = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (value != null)
                    return value.getRowBytes() * value.getHeight();
                else
                    return 0;
            }
                                                                                          
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue != null)
                    // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                    mSoftCache.put(key, new SoftReference(oldValue));
            }
        };
        mSoftCache = new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true) {
            private static final long serialVersionUID = 6040103833179403725L;
            @Override
            protected boolean removeEldestEntry(Entry> eldest) {
                if (size() > SOFT_CACHE_SIZE){    
                    return true;  
                }  
                return false; 
            }
        };
    }
                                                                                  
   
    public Bitmap getBitmapFromCache(String url) {
        Bitmap bitmap;
        //先从硬引用缓存中获取
        synchronized (mLruCache) {
            bitmap = mLruCache.get(url);
            if (bitmap != null) {
                //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
                mLruCache.remove(url);
                mLruCache.put(url, bitmap);
                return bitmap;
            }
        }
        //如果硬引用缓存中找不到,到软引用缓存中找
        synchronized (mSoftCache) { 
            SoftReference bitmapReference = mSoftCache.get(url);
            if (bitmapReference != null) {
                bitmap = bitmapReference.get();
                if (bitmap != null) {
                    //将图片移回硬缓存
                    mLruCache.put(url, bitmap);
                    mSoftCache.remove(url);
                    return bitmap;
                } else {
                    mSoftCache.remove(url);
                }
            }
        }
        return null;
    } 
                                                                                  
   
    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (bitmap != null) {
            synchronized (mLruCache) {
                mLruCache.put(url, bitmap);
            }
        }
    }
                                                                                  
    public void clearCache() {
        mSoftCache.clear();
    }
}
接下来看内存缓存类:ImageMemoryCache
public class ImageFileCache {
    private static final String CACHDIR = "ImgCach";
    private static final String WHOLESALE_CONV = ".cach";
                                                            
    private static final int MB = 1024*1024;
    private static final int CACHE_SIZE = 10;
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
                                                                
    public ImageFileCache() {
        //清理文件缓存
        removeCache(getDirectory());
    }
                                                                
   
    public Bitmap getImage(final String url) {    
        final String path = getDirectory() + "/" + convertUrlToFileName(url);
        File file = new File(path);
        if (file.exists()) {
            Bitmap bmp = BitmapFactory.decodeFile(path);
            if (bmp == null) {
                file.delete();
            } else {
                updateFileTime(path);
                return bmp;
            }
        }
        return null;
    }
                                                                
   
    public void saveBitmap(Bitmap bm, String url) {
        if (bm == null) {
            return;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足
            return;
        }
        String filename = convertUrlToFileName(url);
        String dir = getDirectory();
        File dirFile = new File(dir);
        if (!dirFile.exists())
            dirFile.mkdirs();
        File file = new File(dir +"/" + filename);
        try {
            file.createNewFile();
            OutputStream outStream = new FileOutputStream(file);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
            outStream.flush();
            outStream.close();
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }
    } 
                                                                
   
    private boolean removeCache(String dirPath) {
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if (files == null) {
            return true;
        }
        if (!android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED)) {
            return false;
        }
                                                            
        int dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
                                                            
        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            int removeFactor = (int) ((0.4 * files.length) + 1);
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }
                                                            
        if (freeSpaceOnSd() <= CACHE_SIZE) {
            return false;
        }
                                                                    
        return true;
    }
                                                                
   
    public void updateFileTime(String path) {
        File file = new File(path);
        long newModifiedTime = System.currentTimeMillis();
        file.setLastModified(newModifiedTime);
    }
                                                                
   
    private int freeSpaceOnSd() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
        double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
        return (int) sdFreeMB;
    } 
                                                                
   
    private String convertUrlToFileName(String url) {
        String[] strs = url.split("/");
        return strs[strs.length - 1] + WHOLESALE_CONV;
    }
                                                                
   
    private String getDirectory() {
        String dir = getSDPath() + "/" + CACHDIR;
        return dir;
    }
                                                                
   
    private String getSDPath() {
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED);  //判断sd卡是否存在
        if (sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();  //获取根目录
        }
        if (sdDir != null) {
            return sdDir.toString();
        } else {
            return "";
        }
    } 
                                                            
   
    private class FileLastModifSort implements Comparator {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }
                                                            
}


从网络获取图片:
public class ImageGetFromHttp {
    private static final String LOG_TAG = "ImageGetFromHttp";
                                                           
    public static Bitmap downloadBitmap(String url) {
        final HttpClient client = new DefaultHttpClient();
        final HttpGet getRequest = new HttpGet(url);
                                                               
        try {
            HttpResponse response = client.execute(getRequest);
            final int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) {
                Log.w(LOG_TAG, "Error " + statusCode + " while retrieving bitmap from " + url);
                return null;
            }
                                                                   
            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream inputStream = null;
                try {
                    inputStream = entity.getContent();
                    FilterInputStream fit = new FlushedInputStream(inputStream);
                    return BitmapFactory.decodeStream(fit);
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                        inputStream = null;
                    }
                    entity.consumeContent();
                }
            }
        } catch (IOException e) {
            getRequest.abort();
            Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
        } catch (IllegalStateException e) {
            getRequest.abort();
            Log.w(LOG_TAG, "Incorrect URL: " + url);
        } catch (Exception e) {
            getRequest.abort();
            Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
        } finally {
            client.getConnectionManager().shutdown();
        }
        return null;
    }
                                                       
   
    static class FlushedInputStream extends FilterInputStream {
        public FlushedInputStream(InputStream inputStream) {
            super(inputStream);
        }
                                                       
        @Override
        public long skip(long n) throws IOException {
            long totalBytesSkipped = 0L;
            while (totalBytesSkipped < n) {
                long bytesSkipped = in.skip(n - totalBytesSkipped);
                if (bytesSkipped == 0L) {
                    int b = read();
                    if (b < 0) {
                        break;  // we reached EOF
                    } else {
                        bytesSkipped = 1; // we read one byte
                    }
                }
                totalBytesSkipped += bytesSkipped;
            }
            return totalBytesSkipped;
        }
    }
}
最后,获取一张图片的流程就如下代码所示:


public Bitmap getBitmap(String url) {
    // 从内存缓存中获取图片
    Bitmap result = memoryCache.getBitmapFromCache(url);
    if (result == null) {
        // 文件缓存中获取
        result = fileCache.getImage(url);
        if (result == null) {
            // 从网络获取
            result = ImageGetFromHttp.downloadBitmap(url);
            if (result != null) {
                fileCache.saveBitmap(result, url);
                memoryCache.addBitmapToCache(url, result);
            }
        } else {
            // 添加到内存缓存
            memoryCache.addBitmapToCache(url, result);
        }
    }
    return result;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值