项目作者: JiaYang627

项目描述 :
Bitmap的加载和Cache
高级语言:
项目地址: git://github.com/JiaYang627/Bitmap-Loading-And-Cache.git
创建时间: 2017-10-30T03:03:27Z
项目社区:https://github.com/JiaYang627/Bitmap-Loading-And-Cache

开源协议:

下载


Bitmap-Loading-And-Cache

Bitmap的加载和Cache

Bitmap的高效加载

Bitmap 在Android中指的是一张照片,可以是Png格式也可以是Jpg等其他常见的图片格式。那么如何加载一个图片呢?
BitmapFactory类提供了四种方法:

  • decodeFile 从文件系统
  • decodeResource 从资源
  • decodeStream 从输入流
  • decodeByteArray 从字节数组

其中decodeFile 和 decodeResource又间接调用了decodeStream方法,这四种方法最终是在Android的底层实现的。对应着BitmapFactory类中的几个native方法。

高效加载Bitmap的实现 其核心思想很简单:

通过BitmapFactory.Options 使用其 inSampleSize(采样率)来缩放大图片。通过inSampleSize缩放后可以降低内存的占用从而在一定程度上避免OOM(Out Of Memory Error),提高Bitmap加载时的性能。

附上一段代码(Button按钮点击,ImageView加载资源图片 手机:小米NOTE Pro)

  1. @Override
  2. protected void onCreate(@Nullable Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_bitmap);
  5. mImageView = (ImageView) findViewById(R.id.imageView);
  6. Point point = new Point();
  7. getWindowManager().getDefaultDisplay().getSize(point);
  8. mScreenWidth = point.x; // 屏幕宽度 1440
  9. mScreenHeight = point.y; // 屏幕高度 2560
  10. Log.e("BitmapActivity", String.valueOf(mScreenHeight) + "," + String.valueOf(mScreenWidth));
  11. }
  12. public void loading(View view) {
  13. BitmapFactory.Options options= new BitmapFactory.Options();
  14. // options.inJustDecodeBounds设置为true,不会真正的将图片加载到内存中去
  15. options.inJustDecodeBounds = true;
  16. BitmapFactory.decodeResource(getResources(), R.mipmap.dog, options);
  17. int outHeight = options.outHeight; // 图片的高度 3200
  18. int outWidth = options.outWidth; // 图片的宽度 2400
  19. int heightScale = outHeight / mScreenHeight; // 高度比例
  20. int widthScale = outWidth / mScreenWidth; // 宽度比例
  21. int scale = widthScale > heightScale ? widthScale : heightScale;
  22. // 将算好的采样率设置
  23. options.inSampleSize = scale;
  24. options.inJustDecodeBounds = false;
  25. Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dog, options);
  26. mImageView.setImageBitmap(bitmap);
  27. }

Bitmap-dog

Bitmap-ui

Android的缓存策略

缓存策略在Android中有着广泛的使用场景。但为了避免下载图片消耗过多的流量,缓存策略此时就变的很重要。当程序第一次从网络加载图片后,将其缓存到存储设备上,下次使用的时候就不必再从网络拉取,为了提高用户体验,往往还会降图片再在内存中缓存一份,这样当应用打算从网络请求一张图片的时候,会先从内存中获取,内存没有就从存储设备获取,存储设备没有就在从网络上拉取。

LRU算法(Least Recently Used):近期最少使用算法。其核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
LRU算法缓存有两种:LruCache(内存缓存) 和 DishLruCache(存储设备缓存),通过二者的完美结合,就能实现一个具有很高使用价值的ImageLoader。

内存缓存LruCache

LruCache 是一个线程安全的广泛类,其内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。

  • 强引用:直接的对象引用;
  • 软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收;
  • 弱引用:当一个对象只有弱引用存在时,此对象会随时被gc回收;

LruCache的定义:

  1. public class LruCache<K,V>{
  2. private final LinkedHashMap<K,V> map;
  3. ...
  4. }
  • LruCache的实现也很简单,附上LruCache的典型初始化过程:
  1. int maxMemory = (int) Runtime.getRuntime().maxMemory();
  2. int cacheSise =maxMemory / 8;
  3. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  4. //计算缓存对象的大小
  5. @Override
  6. protected int sizeOf(String key, Bitmap bitmap) {
  7. // 单位是MB
  8. return bitmap.getRowBytes() * bitmap.getHeight() / bitmap.getByteCount();
  9. }
  10. //移除旧缓存时调用
  11. @Override
  12. protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
  13. super.entryRemoved(evicted, key, oldValue, newValue);
  14. // 资源回收的工作
  15. }
  16. };
  • LruCache的添加和获得一个缓存对象也很简单:
  1. mMemoryCache.get(key); // 获得缓存
  2. mMemoryCache.put(key , bitmap); // 添加
  3. mMemoryCache.remove(key); // 删除

存储设备缓存DiskLruCache

DiskLruCache 磁盘缓存:通过将缓存对象写入文件系统从而实现缓存的效果。
DiskLruCache得到了Android官方文档的推荐,但不属于AndroidSDK的一部分,源码可从如下网址得到

  1. https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
  • DiskLruCache的创建
  1. private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;//磁盘缓存50M大小
  2. File diskCacheDir = getExternalCacheDir();
  3. if (!diskCacheDir.exists()){
  4. diskCacheDir.mkdirs();
  5. }
  6. /**
  7. * Opens the cache in {@code directory}, creating a cache if none exists
  8. * there.
  9. *
  10. * @param directory a writable directory 缓存目录
  11. * @param appVersion 应用版本号
  12. * @param valueCount the number of values per cache entry. Must be positive.
  13. * @param maxSize the maximum number of bytes this cache should use to store
  14. * @throws IOException if reading or writing the cache directory fails
  15. */
  16. DiskLruCache diskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
  • DiskLruCache的缓存添加

DiskLruCache缓存添加是通过Editor完成的。当使用Editor编辑完缓存对象后,记得使用commit().

注意:图片的url可能含有特殊字符,所以一般采用url的md5值做为key。

  1. String imgUrl = "";
  2. String key = MD5Util.getMd5Value(imgUrl);
  3. DiskLruCache.Editor editor = diskLruCache.edit(key);
  4. if (editor != null) {
  5. editor.getString(0);
  6. }
  7. editor.commit();//提交
  8. editor.abort();//回退
  9. diskLruCache.flush();//刷新
  • DiskLruCache的缓存查找

通过DiskLruCache.get获取一个SnapShot对象,再使用Snapshot对象即可获得缓存的文件输入流。

关于FileInputStream(文件描述符):直接使用BitmapFactory.OptionsFileInputStream进行缩放会出现问题,因为FileInputStream是一种有序的文件流,两次decodeStream调用会影响文件流的位置属性,导致第二次decodeStream时得到的是null。为了解决这个问题,可以通过文件流来得到它所对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor来加载一张缩放后的图片。

附上一段代码:

  1. String key = MD5Util.getMd5Value(imgUrl);
  2. Bitmap bitmap = null;
  3. DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
  4. if (snapshot != null) {
  5. FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
  6. //从文件流中获取文件描述符
  7. FileDescriptor fileDescriptor = fileInputStream.getFD();
  8. bitmap = decodeSampledBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
  9. if (bitmap != null) {
  10. addBitmapToMemoryCache(key,bitmap);
  11. }
  12. }
  • 最后附上自写的ImageLoader

JYImageLoader