布景
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 屏幕适配,那些你必须把握的稀碎常识点。
削减内存占用
-
紧缩,采样率 — 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倍”。
-
复用现已拓荒出的内存,一般咱们都会运用 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(); }
内存加载战略
- 首要运用内存缓存 LruCache
- 运用复用缓存(内存缓存中 LruCache 中移除出来的,加入到复用缓存中)
- 运用磁盘缓存(DiskLruCache)
- 假如都找不到,再从网络获取(然后放入内存,放入磁盘)
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 内存,但是经过上面轮询堵塞的方法,能够更快的进行内存收回。
内存收回
- 2.3~4.4 能够经过 inInputShareable、inPurgeable 让 Bitmap 的内存在 native 层分配(已废弃)
- 8.0 之前的 Bitmap 像素数据根本存储在 Java heap
- 8.0 之后的 Bitmap 像素数据根本存储在 native heap,NativeAllocationRegistry 是 Android 8.0 引进的一种辅助自动收回 native 内存的一种机制,当 Java 目标由于 GC 被收回后,NativeAllocationRegistry 能够辅助收回 Java 目标所请求的 native 内存
- 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的初级,中级,高级问法,你都把握了吗?