Android 性能优化(十)图片加载和大图片缓存机制OOM完美解决方案LruCache&DiskLruCache

本文介绍三种图片加载优化方式:图片缩放以节约内存、内存缓存(LruCache)及硬盘缓存(DiskLruCache)。重点讲解了BitmapFactory.Options中的inSampleSize属性,LruCache的实现原理及DiskLruCache的使用方法。
摘要由CSDN通过智能技术生成

主要的三种方式:图片缩放节约内存,内存缓存,硬盘缓存
一.inSampleSize(缩放值)

经过研究,发现,Options中有个属性inJustDecodeBounds,研究了一下,终于明白是什么意思了,SDK中的E文是这么说的
      If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.
  意思就是说如果该值设为true那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息,那么相应的方法也就出来了,通过设置inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),然后就可以取图片了,这里要注意的是,inSampleSize 可能小于0,必须做判断。

        inJustDecodeBounds :如果设置为true则表示decode函数不会生成bitmap对象,仅是将图像相关的参数填充到option对象里,这样我们就可以在不生成bitmap而获取到图像的相关参数了。

为什么用这个图片缩放,如下列子:
ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。
大像素的图片变小
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                     int reqWidth, int reqHeight) {
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    // 调用上面定义的方法计算inSampleSize    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    // 使用获取到的inSampleSize值再次解析图片
    options.inJustDecodeBounds = false;
    /**采用的加载图片的方法BitmapFactory.decodeResource(res, resId, options)*/
    return BitmapFactory.decodeResource(res, resId, options);
}
二.内存缓存 LruCache  
LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要 算法 原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
LRU的含义:Least Recently Used,近期最少使用算法,主要是对 LinkedHashMap的封装,和加了缓存大小的封装!
主要方法:
get:通过键得到值
put:插入键和匹配的值

LinkedHashMap集合,可以通过键找到值, LinkedHashMap的原理分析:
/**
 * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
 */
private LruCache<String, Bitmap> mMemoryCache;

public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
      GridView photoWall) {
   super(context, textViewResourceId, objects);
   mPhotoWall = photoWall;
   taskCollection = new HashSet<BitmapWorkerTask>();
   // 获取应用程序最大可用内存
   int maxMemory = (int) Runtime.getRuntime().maxMemory();
   int cacheSize = maxMemory / 8;
   // 设置图片缓存大小为程序最大可用内存的1/8
   mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

/**
 * 将一张图片存储到LruCache中。
 * 
 * @param key
 *            LruCache的键,这里传入图片的URL地址。
 * @param bitmap
 *            LruCache的键,这里传入从网络上下载的Bitmap对象。
 */
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
   if (getBitmapFromMemoryCache(key) == null) {
      mMemoryCache.put(key, bitmap);
   }
}

/**
 * LruCache中获取一张图片,如果不存在就返回null * 
 * @param key
 *            LruCache的键,这里传入图片的URL地址。
 * @return 对应传入键的Bitmap对象,或者null */
public Bitmap getBitmapFromMemoryCache(String key) {
   return mMemoryCache.get(key);
}

LruCache 底层原理

LruCache 使用一个 LinkedHashMap 简单的实现内存的缓存,没有软引用,都是强引用。如果添 
加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。 
maxSize 是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。 
size 在添加和移除缓存都被更新值,他通过 safeSizeOf 这个方法更新值。safeSizeOf 默认返回 1, 
但一般我们会根据 maxSize 重写这个方法,比如认为 maxSize 代表是 KB 的话,那么就以 KB 为单 
位返回该项所占的内存大小。 
除异常外首先会判断 size 是否超过 maxSize,如果超过了就取出最先插入的缓存,如果不为空就 
删掉,并把 size 减去该项所占的大小。这个操作将一直循环下去,直到 size 比 maxSize 小或者缓存 
为空。



DiskLruCache也是采用: LinkedHashMap;
由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来
DiskLruCache.Snapshot:对流的封装

/**
 * 图片硬盘缓存核心类。
 */
private DiskLruCache mDiskLruCache;

public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
      GridView photoWall) {
   super(context, textViewResourceId, objects);
   mPhotoWall = photoWall;
   taskCollection = new HashSet<BitmapWorkerTask>();
   // 获取应用程序最大可用内存
   int maxMemory = (int) Runtime.getRuntime().maxMemory();
   int cacheSize = maxMemory / 8;
   try {
      // 获取图片缓存路径
      File cacheDir = getDiskCacheDir(context, "thumb");
      if (!cacheDir.exists()) {
         cacheDir.mkdirs();
      }
      // 创建DiskLruCache实例,初始化缓存数据
      /**每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。*/
      mDiskLruCache = DiskLruCache
            .open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapShot = null;
try {
   // 生成图片URL对应的key
   final String key = hashKeyForDisk(imageUrl);
   // 查找key对应的缓存
   snapShot = mDiskLruCache.get(key);
   if (snapShot == null) {
      // 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
      DiskLruCache.Editor editor = mDiskLruCache.edit(key);
      if (editor != null) {
         OutputStream outputStream = editor.newOutputStream(0);
         if (downloadUrlToStream(imageUrl, outputStream)) {
            editor.commit();
         } else {
            editor.abort();
         }
      }
      // 缓存被写入后,再次查找key对应的缓存
      snapShot = mDiskLruCache.get(key);
   }
   if (snapShot != null) {
      fileInputStream = (FileInputStream) snapShot.getInputStream(0);
      fileDescriptor = fileInputStream.getFD();

DiskLruCache的主要方法:
1.get方法:得到缓存
snapShot = mDiskLruCache.get(key);
2.Edit方法:写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
3. remove方法: 移除缓存
4. delete()方法: 清理缓存
这个方法用于将所有的缓存数据全部删除

加密Key值: key一定要用md5加密,因为图片的url一般都会有特殊字符,是不符合这里的验证的。
/**
 * 使用MD5算法对传入的key(图片地址)进行加密并返回。
 */
public String hashKeyForDisk(String key) {
   
   Log.d("DISK", "key="+key);
   
   String cacheKey;
   try {
      final MessageDigest mDigest = MessageDigest.getInstance("MD5");
      mDigest.update(key.getBytes());
      cacheKey = bytesToHexString(mDigest.digest());
   } catch (NoSuchAlgorithmException e) {
      cacheKey = String.valueOf(key.hashCode());
   }
   
   Log.d("DISK", "cacheKey="+cacheKey);
   
   return cacheKey;
}

解读journal文件:

解决的问题:比如写文件的时候 手机突然没电了之类的,你得保证文件正确性,唯一性等等。


1.每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。
2.然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,
3.调用abort()方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。
4.每当我们调用get()方法去读取一条缓存数据时,就会向journal文件中写入一条READ记录

四:滚动的时候不去下载图片,静止的时候才去下载图片


五:图片回收

bitmap.recycle();

六:图片压缩
bitmap.compress();

总结:
缓存机制:内存--文件---网络
如果启用了,加载时,先从内存中查找,然后从硬盘上找,最后去网络下载。下载完成后,别忘了写入硬盘,加入内存缓存。如果没有启用,那么就直接从网络压缩获取,加入内存即可。

AS代码地址:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值