Bitmap的加载和缓存

作者: 一只攻城狮 | 来源:发表于2017-11-13 17:44 被阅读44次

    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)

    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_bitmap);
            mImageView = (ImageView) findViewById(R.id.imageView);
            Point point = new Point();
            getWindowManager().getDefaultDisplay().getSize(point);
            mScreenWidth = point.x;     // 屏幕宽度 1440
            mScreenHeight = point.y;    // 屏幕高度 2560
    
            Log.e("BitmapActivity", String.valueOf(mScreenHeight) + "," + String.valueOf(mScreenWidth));
        }
    
     public void loading(View view) {
            BitmapFactory.Options options= new BitmapFactory.Options();
            
            // options.inJustDecodeBounds设置为true,不会真正的将图片加载到内存中去
            options.inJustDecodeBounds = true;
    
            BitmapFactory.decodeResource(getResources(), R.mipmap.dog, options);
    
    
            int outHeight = options.outHeight;  // 图片的高度 3200
            int outWidth = options.outWidth;    // 图片的宽度 2400
    
            int heightScale = outHeight / mScreenHeight; // 高度比例 
            int widthScale = outWidth / mScreenWidth;   // 宽度比例
            int scale = widthScale > heightScale ? widthScale : heightScale;
    
            // 将算好的采样率设置
            options.inSampleSize = scale;
            options.inJustDecodeBounds = false;
    
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dog, options);
            mImageView.setImageBitmap(bitmap);
        }
    
    Bitmap-dog Bitmap-ui

    Android的缓存策略

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

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

    内存缓存LruCache

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

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

    LruCache的定义:

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

    存储设备缓存DiskLruCache

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

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

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

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

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

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

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

    附上一段代码:

    String key = MD5Util.getMd5Value(imgUrl);
    
    Bitmap bitmap = null;
    
    DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
    if (snapshot != null) {
        FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
        //从文件流中获取文件描述符
        FileDescriptor fileDescriptor = fileInputStream.getFD();
        bitmap = decodeSampledBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
        if (bitmap != null) {
            addBitmapToMemoryCache(key,bitmap);
        }
    }
    
    • 自写一个图片加载JYImageLoader
      实现的功能
      • 图片压缩
      • 网络获取图片
      • 异步加载图片

    JYImageLoader

    AndroidUI

    JYImageLoader

    相关文章

      网友评论

        本文标题:Bitmap的加载和缓存

        本文链接:https://www.haomeiwen.com/subject/ajozmxtx.html