0*7wBRYeSryL8YT5u_.png

布景

Bitmap 是 Android 体系中的图画处理中最重要类之一。关于大多数 App,如何高效加载 Bitmap 显得至关重要。Bitmap 能够获取图画文件信息,对图画进行剪切、旋转、缩放,紧缩等操作,并能够以指定格局保存图画文件。现在现已有许多主流的结构,如 Glide,Fresco,Picasso 等帮咱们快速完成。其实这其中都包含了图片高效加载的战略,缓存战略等。这些结构当然很优秀,但有时分需求咱们自己完成自己的图片加载结构的时分,咱们也应该胸有成竹。

Bitmap 内存占用

Bitmap 的内存占用 = 宽 * 高 * 单位像素所占用字节,只与这三个因素有关,和图片是webp,png,jpg 没有联系。

Bitmap Config

颜色用 ARGB 来表明,A 表明 Alpha,即透明度;R 表明 red,即红色;G 表明 green,即绿色;B 表明 blue,即蓝色。Bitmap 的颜色也是用 ARGB 来表明的

Bitmap.Config中有Bitmap.Config.ALPHA_8、Bitmap.Config.RGB_565、Bitmap.Config.ARGB_4444、 Bitmap.Config.ARGB_8888有四个枚举变量。

Bitmap.Config.ALPHA_8表明:每个像素占8位,没有颜色,只需透明度A-8,共8位。
Bitmap.Config.ARGB_4444表明:每个像素占16位,A-4,R-4,G-4,B-4,共4+4+4+4=16位。
Bitmap.Config.RGB_565表明:每个像素占16位,没有透明度,R-5,G-6,B-5,共5+6+5=16位。
Bitmap.Config.ARGB_8888表明:每个像素占32位,A-8,R-8,G-8,B-8,共8+8+8+8=32位。
位数越高,那么可存储的颜色信息越多,图画也就越传神。

drawable 不同分辨率文件夹放置和 Bitmap 巨细的联系

详细的联系能够参考我的这篇文章Android 屏幕适配,那些你必须把握的稀碎常识点。

削减内存占用

  1. 紧缩,采样率 — inSampleSize,大于等于 1,经过对它的设置,对图片的像素的高和宽进行缩放,官方文档指出,inSampleSize 的取值应该总是 2 的指数,如 1,2,4,8 等。假如外界传入的 inSampleSize 的值不为 2 的指数,那么体系会向下取整并选择一个最接近 2 的指数来替代。同时图片不宜直接加载到内存中。

    一般咱们优化Bitmap时,当需求做性能优化或许防止 OOM(Out Of Memory),咱们一般会运用Bitmap.Config.RGB_565 这个配置,由于Bitmap.Config.ALPHA_8 只需透明度,显现一般图片没有意义,Bitmap.Config.ARGB_4444 显现图片不清楚,Bitmap.Config.ARGB_8888 占用内存最多。

    CompressFormat解析

    Bitmap.CompressFormat.JPEG:表明以 JPEG 紧缩算法进行图画紧缩,紧缩后的格局能够是”.jpg”或许”.jpeg”,是一种有损紧缩。

    Bitmap.CompressFormat.PNG:表明以 PNG 紧缩算法进行图画紧缩,紧缩后的格局能够是”.png”,是一种无损紧缩。

    Bitmap.CompressFormat.WEBP:表明以 WebP 紧缩算法进行图画紧缩,紧缩后的格局能够是”.webp”,是一种有损紧缩,质量相同的情况下,WebP 格局图画的体积要比 JPEG 格局图画小40%。美中不足的是,WebP 格局图画的编码时间“比 JPEG 格局图画长8倍”。

  2. 复用现已拓荒出的内存,一般咱们都会运用 LruCache(最近最少运用) 来缓存 Bitmap,当某个 Bitmap 被移除后,会调用 entryRemoved 办法进行移除,这个时分咱们能够把这个移除的 Bitmap 放到复用池中进行复用(由于现已拓荒出内存了),需求留意的是,我的复用池并没有设置巨细,是由于每次 getReusable() 后都会将其移除,而且也不用忧虑复用池太大的问题(bitmap 很简单被复用,安卓体系的优化)。

    //复用办法    
    public static Bitmap resizeBitmap(Context context, int id, int maxW, int maxH, boolean hasAlpha, Bitmap reusable) {
    Resources resources = context.getResources();
    BitmapFactory.Options options = new BitmapFactory.Options();
    // 设置为true后,再去解析,就只解析 out 参数
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(resources, id, options);
    int w = options.outWidth;
    int h = options.outHeight;
    options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH);
    if (!hasAlpha) {
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    }
    options.inJustDecodeBounds = false;
    // 复用, inMutable 为true 表明易变
    options.inMutable = true;
    options.inBitmap = reusable;
    return BitmapFactory.decodeResource(resources, id, options);
    }
    /**
    * 3.0 之前不能复用
    * 3.0-4.4 宽高一样,inSampleSize = 1
    * 4.4 只需小于等于就行了
    *
    * @param w
    * @param h
    * @param inSampleSize
    * @return
    */
    public Bitmap getReusable(int w, int h, int inSampleSize) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
    return null;
    }
    Bitmap reusable = null;
    Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
    while (iterator.hasNext()) {
    Bitmap bitmap = iterator.next().get();
    if (bitmap != null) {
    if (checkInBitmap(bitmap, w, h, inSampleSize)) {
    reusable = bitmap;
    iterator.remove();
    break;
    }
    } else {
    iterator.remove();
    }
    }
    return reusable;
    }
    /**
    * 校验bitmap 是否满意条件
    *
    * @param bitmap
    * @param w
    * @param h
    * @param inSampleSize
    * @return
    */
    private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
    }
    if (inSampleSize > 1) {
    w /= inSampleSize;
    h /= inSampleSize;
    }
    int byteCount = w * h * getBytesPerPixel(bitmap.getConfig());
    // 图片内存 体系分配内存
    return byteCount <= bitmap.getAllocationByteCount();
    }
    

内存加载战略

  1. 首要运用内存缓存 LruCache
  2. 运用复用缓存(内存缓存中 LruCache 中移除出来的,加入到复用缓存中)
  3. 运用磁盘缓存(DiskLruCache)
  4. 假如都找不到,再从网络获取(然后放入内存,放入磁盘)
private LruCache<String, Bitmap> lruCache;
private Set<WeakReference<Bitmap>> reusablePool;
private DiskLruCache diskLruCache;
public void init(Context context, String dir) {
// 复用池
reusablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
// 内存巨细
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
//memoryClass * 1024 * 1024 / 8
lruCache = new LruCache<String, Bitmap>(memoryClass * 1024 * 1024 / 8) {
// 回来的一张图片巨细
@Override
protected int sizeOf(String key, Bitmap value) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {
// 3.0 bitmap 缓存 native
// <8.0  bitmap 缓存 java
// 8.0 native
// 将bitmap 和一个行列进行相关(弱引证),当被收回的时分行列中会有该bitmap的引证,然后进行获取,收回bitmap
reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue()));
} else {
oldValue.recycle();  //不行复用,就进行收回
}
}
};
try {
diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
private ReferenceQueue<Bitmap> getReferenceQueue() {
if (referenceQueue == null) {  //假如为空,就进行创立,创立一个线程,一直在轮询查找是否有被收回的bitmap 需求收回,前面调用reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue())); 将bitmap 和一个行列进行相关(弱引证),当被收回的时分行列中会有该bitmap的引证,然后进行获取,收回bitmap
referenceQueue = new ReferenceQueue<>();
new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown) {
try {
Reference<? extends Bitmap> remove = referenceQueue.remove();//堵塞办法
Bitmap bitmap = remove.get();
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
return referenceQueue;
}

虽然在 android 8.0 之后,NativeAllocationRegistry能够辅助收回Java目标所请求的native 内存,但是经过上面轮询堵塞的方法,能够更快的进行内存收回。

内存收回

  1. 2.3~4.4 能够经过 inInputShareable、inPurgeable 让 Bitmap 的内存在 native 层分配(已废弃)
  2. 8.0 之前的 Bitmap 像素数据根本存储在 Java heap
  3. 8.0 之后的 Bitmap 像素数据根本存储在 native heap,NativeAllocationRegistry 是 Android 8.0 引进的一种辅助自动收回 native 内存的一种机制,当 Java 目标由于 GC 被收回后,NativeAllocationRegistry 能够辅助收回 Java 目标所请求的 native 内存
  4. 2.3 之前的像素存储需求的内存是在 native 上分配的,而且生命周期不太可控,或许需求用户自己收回。 2.3-7.1 之间,Bitmap 的像素存储在 Dalvik 的 Java 堆上,当然,4.4 之前的甚至能在匿名共享内存上分配(Fresco 采用),而8.0 之后的像素内存又重新回到 native 上去分配,不需求用户主动收回,8.0 之后图画资源的办理愈加优秀,极大降低了 OOM。

大图加载

BitmapRegionDecoder 大图加载利器

看看是横向还是纵向滑动,每次加载固定一小块区域(Rect),然后结合手势,判断是什么方向的移动,然后归纳进行处理。

总结

Bitmap 其实是个很琐碎的常识点,触及许多方面的常识,把握好这些,后面咱们在学习优秀的第三方结构也会更简单了,喜欢的点个赞吧~

我的其他文章

Android 屏幕适配,那些你必须把握的稀碎常识点

Android 轻量级存储方案的宿世此生

性能优化:为什么要运用SparseArray和ArrayMap替代HashMap?

Activity的初级,中级,高级问法,你都把握了吗?