", "(JIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
gBitmap_getAllocationByteCountMethodID = GetMethodIDOrDie(env, gBitmap_class, "getAllocationByteCount", "()I");
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
}
```
8.0 的Bitmap创建就两个点:
1. 创建native层Bitmap,在native堆申请内存。
2. 通过JNI创建java层Bitmap对象,这个对象在java堆中分配内存。
像素数据是存在native层Bitmap,也就是证明8.0的Bitmap像素数据存在native堆中。
###### 7.0 Bitmap
直接看native层的方法,
```
//JNI动态注册
static const JNINativeMethod gBitmapMethods[] = {
{ "nativeCreate", "([IIIIIIZ)Landroid/graphics/Bitmap;",
(void*)Bitmap_creator },
...
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable) {
...
//1.通过这个方法来创建native层Bitmap
Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
...
return GraphicsJNI::createBitmap(env, nativeBitmap,
getPremulBitmapCreateFlags(isMutable));
}
```
native层Bitmap 创建是通过`GraphicsJNI::allocateJavaPixelRef`,看看里面是怎么分配的, GraphicsJNI 的实现类是[Graphics.cpp](https://link.juejin.cn/?target=https%3A%2F%2Fwww.androidos.net.cn%2Fandroid%2F7.0.0_r31%2Fxref%2Fframeworks%2Fbase%2Fcore%2Fjni%2Fandroid%2Fgraphics%2FGraphics.cpp "https://www.androidos.net.cn/android/7.0.0_r31/xref/frameworks/base/core/jni/android/graphics/Graphics.cpp")
```
android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();
size_t size;
//计算需要的空间大小
if (!computeAllocationSize(*bitmap, &size)) {
return NULL;
}
// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
// 1. 创建一个数组,通过JNI在java层创建的
jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
gVMRuntime_newNonMovableArray,
gByte_class, size);
...
// 2. 获取创建的数组的地址
jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
...
//3. 创建Bitmap,传这个地址
android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
info, rowBytes, ctable);
wrapper->getSkBitmap(bitmap);
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();
return wrapper;
}
```
可以看到,7.0 像素内存的分配是这样的:
1. 通过JNI调用java层创建一个数组
2. 然后创建native层Bitmap,把数组的地址传进去。
由此说明,7.0 的Bitmap像素数据是放在java堆的。
当然,3.0 以下Bitmap像素内存据说也是放在native堆的,但是需要手动释放native层的Bitmap,也就是需要手动调用recycle方法,native层内存才会被回收。这个大家可以自己去看源码验证。
###### native层Bitmap 回收问题
Java层的Bitmap对象由垃圾回收器自动回收,而native层Bitmap印象中我们是不需要手动回收的,源码中如何处理的呢?
记得有个面试题是这样的:
> 说说final、finally、finalize 的关系
三者除了长得像,其实没有半毛钱关系,final、finally大家都用的比较多,而 `finalize` 用的少,或者没用过,`finalize` 是 Object 类的一个方法,注释是这样的:
```
/**
* Called by the garbage collector on an object when garbage collection
* determines that there are no more references to the object.
* A subclass overrides the {@code finalize} method to dispose of
* system resources or to perform other cleanup.
*
...**/
protected void finalize() throws Throwable { }
```
意思是说,垃圾回收器确认这个对象没有其它地方引用到它的时候,会调用这个对象的`finalize`方法,子类可以重写这个方法,做一些释放资源的操作。
**在6.0以前,Bitmap 就是通过这个finalize 方法来释放native层对象的。** [6.0 Bitmap.java](https://link.juejin.cn/?target=https%3A%2F%2Fwww.androidos.net.cn%2Fandroid%2F6.0.1_r16%2Fxref%2Fframeworks%2Fbase%2Fgraphics%2Fjava%2Fandroid%2Fgraphics%2FBitmap.java "https://www.androidos.net.cn/android/6.0.1_r16/xref/frameworks/base/graphics/java/android/graphics/Bitmap.java")
```
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
...
mNativePtr = nativeBitmap;
//1.创建 BitmapFinalizer
mFinalizer = new BitmapFinalizer(nativeBitmap);
int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
}
private static class BitmapFinalizer {
private long mNativeBitmap;
// Native memory allocated for the duration of the Bitmap,
// if pixel data allocated into native memory, instead of java byte[]
private int mNativeAllocationByteCount;
BitmapFinalizer(long nativeBitmap) {
mNativeBitmap = nativeBitmap;
}
public void setNativeAllocationByteCount(int nativeByteCount) {
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
}
mNativeAllocationByteCount = nativeByteCount;
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
}
}
@Override
public void finalize() {
try {
super.finalize();
} catch (Throwable t) {
// Ignore
} finally {
//2.就是这里了,
setNativeAllocationByteCount(0);
nativeDestructor(mNativeBitmap);
mNativeBitmap = 0;
}
}
}
```
在Bitmap构造方法创建了一个 `BitmapFinalizer`类,重写finalize 方法,在java层Bitmap被回收的时候,BitmapFinalizer 对象也会被回收,finalize 方法肯定会被调用,在里面释放native层Bitmap对象。
6.0 之后做了一些变化,BitmapFinalizer 没有了,被NativeAllocationRegistry取代。
例如 8.0 Bitmap构造方法
```
Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
...
mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
// 创建NativeAllocationRegistry这个类,调用registerNativeAllocation 方法
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
}
```
NativeAllocationRegistry 就不分析了, **不管是BitmapFinalizer 还是NativeAllocationRegistry,目的都是在java层Bitmap被回收的时候,将native层Bitmap对象也回收掉。** 一般情况下我们无需手动调用recycle方法,由GC去盘它即可。
上面分析了Bitmap像素存储位置,我们知道,Android 8.0 之后Bitmap像素内存放在native堆,Bitmap导致OOM的问题基本不会在8.0以上设备出现了(没有内存泄漏的情况下),那8.0 以下设备怎么办?赶紧升级或换手机吧~

我们换手机当然没问题,但是并不是所有人都能跟上Android系统更新的步伐,所以,问题还是要解决~
Fresco 之所以能跟Glide 正面交锋,必然有其独特之处,文中开头列出 Fresco 的优点是:“在5.0以下(最低2.3)系统,Fresco将图片放到一个特别的内存区域(Ashmem区)” 这个Ashmem区是一块匿名共享内存,Fresco 将Bitmap像素放到共享内存去了,共享内存是属于native堆内存。
Fresco 关键源码在 `PlatformDecoderFactory` 这个类
```
public class PlatformDecoderFactory {
/**
* Provide the implementation of the PlatformDecoder for the current platform using the provided
* PoolFactory
*
* @param poolFactory The PoolFactory
* @return The PlatformDecoder implementation
*/
public static PlatformDecoder buildPlatformDecoder(
PoolFactory poolFactory, boolean gingerbreadDecoderEnabled) {
//8.0 以上用 OreoDecoder 这个解码器
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
return new OreoDecoder(
poolFactory.getBitmapPool(), maxNumThreads, new Pools.SynchronizedPool<>(maxNumThreads));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//大于5.0小于8.0用 ArtDecoder 解码器
int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
return new ArtDecoder(
poolFactory.getBitmapPool(), maxNumThreads, new Pools.SynchronizedPool<>(maxNumThreads));
} else {
if (gingerbreadDecoderEnabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
//小于4.4 用 GingerbreadPurgeableDecoder 解码器
return new GingerbreadPurgeableDecoder();
} else {
//这个就是4.4到5.0 用的解码器了
return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
}
}
}
}
```
8.0 先不看了,看一下 4.4 以下是怎么得到Bitmap的,看下`GingerbreadPurgeableDecoder`这个类有个获取Bitmap的方法
```
//GingerbreadPurgeableDecoder
private Bitmap decodeFileDescriptorAsPurgeable(
CloseableReference bytesRef,
int inputLength,
byte[] suffix,
BitmapFactory.Options options) {
// MemoryFile :匿名共享内存
MemoryFile memoryFile = null;
try {
//将图片数据拷贝到匿名共享内存
memoryFile = copyToMemoryFile(bytesRef, inputLength, suffix);
FileDescriptor fd = getMemoryFileDescriptor(memoryFile);
if (mWebpBitmapFactory != null) {
// 创建Bitmap,Fresco自己写了一套创建Bitmap方法
Bitmap bitmap = mWebpBitmapFactory.decodeFileDescriptor(fd, null, options);
return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null");
} else {
throw new IllegalStateException("WebpBitmapFactory is null");
}
}
}
```
捋一捋,4.4以下,Fresco 使用匿名共享内存来保存Bitmap数据,首先将图片数据拷贝到匿名共享内存中,然后使用Fresco自己写的加载Bitmap的方法。
Fresco对不同Android版本使用不同的方式去加载Bitmap,至于4.4-5.0,5.0-8.0,8.0 以上,对应另外三个解码器,大家可以从`PlatformDecoderFactory` 这个类入手,自己去分析,思考为什么不同平台要分这么多个解码器,8.0 以下都用匿名共享内存不好吗?期待你在评论区跟大家分享~
### 2.5 ImageView 内存泄露
> 曾经在Vivo驻场开发,带有头像功能的页面被测出内存泄漏,原因是SDK中有个加载网络头像的方法,持有ImageView引用导致的。
当然,修改也比较简单粗暴,**将ImageView用WeakReference修饰**就完事了。
事实上,这种方式虽然解决了内存泄露问题,但是并不完美,例如在界面退出的时候,我们除了希望ImageView被回收,同时希望加载图片的任务可以取消,队未执行的任务可以移除。
Glide的做法是监听生命周期回调,看 `RequestManager` 这个类
```
public void onDestroy() {
targetTracker.onDestroy();
for (Target> target : targetTracker.getAll()) {
//清理任务
clear(target);
}
targetTracker.clear();
requestTracker.clearRequests();
lifecycle.removeListener(this);
lifecycle.removeListener(connectivityMonitor);
mainHandler.removeCallbacks(addSelfToLifecycle);
glide.unregisterRequestManager(this);
}
```
在Activity/fragment 销毁的时候,取消图片加载任务,细节大家可以自己去看源码。
### 2.6 列表加载问题
#### 图片错乱
由于RecyclerView或者LIstView的复用机制,网络加载图片开始的时候ImageView是第一个item的,加载成功之后ImageView由于复用可能跑到第10个item去了,在第10个item显示第一个item的图片肯定是错的。
常规的做法是给ImageView设置tag,tag一般是图片地址,更新ImageView之前判断tag是否跟url一致。
当然,可以在item从列表消失的时候,取消对应的图片加载任务。要考虑放在图片加载框架做还是放在UI做比较合适。
#### 线程池任务过多
列表滑动,会有很多图片请求,如果是第一次进入,没有缓存,那么队列会有很多任务在等待。所以在请求网络图片之前,需要判断队列中是否已经存在该任务,存在则不加到队列去。
总结
--
本文通过Glide开题,分析一个图片加载框架必要的需求,以及各个需求涉及到哪些技术和原理。
* 异步加载:最少两个线程池
* 切换到主线程:Handler
* 缓存:LruCache、DiskLruCache,涉及到LinkHashMap原理
* 防止OOM:软引用、LruCache、图片压缩没展开讲、Bitmap像素存储位置源码分析、Fresco部分源码分析
* 内存泄露:注意ImageView的正确引用,生命周期管理
* 列表滑动加载的问题:加载错乱用tag、队满任务存在则不添加