大家都知道,bitmap容易引起oom。具体原因是什么呢?因为每个进程的heap是有限制的,现在一般都在一两百兆。而一张图片加载到内存中需要占用多少内存呢?以一张10241024宽高,ARGB8888的图片为例,占用内存为10241024*4 = 4M,那么只需创建几十个bitmap就会oom。
但是,这并不是我要说的。今天我在自己的华为8.1系统手机上同时创建了200个bitmap,但是并没有oom。用as的profiler查看,发现总内存使用虽然很大,但是java heap才几兆,而Native heap却有几百兆。
先来科普一下java heap 和 native heap。
nativie heap:采用C/C++ 实现,不包含dalvik实例的进程。在这种进程中开辟的内存称为native heap。
java heap:Android中运行于dalvik 虚拟机之上的进程中(也就是我们常说的进程)开辟的内存称为java heap。
在安卓系统3.0以前,bitmap是在native heap中开辟空间来存放像素数据。3.0-7.0是在java heap中。而8.0开始又改成了native heap。这也就解释了8.1的手机上为什么不会oom,因为oom是专门针对java heap的。
之前在研究glide源码的时候,我记得bitmapPool里有一个方法trimMemory,会根据系统的onTrimMemory回调方法中的参数level来进行bitmap的回收,里面调用的是recycle方法。可是官方很早以前就说这个方法不需要调用了。
我们来看一下8.0之后recycle方法的实现
void freePixels() {
mInfo = mBitmap->info();
mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
mAllocationSize = mBitmap->getAllocationByteCount();
mRowBytes = mBitmap->rowBytes();
mGenerationId = mBitmap->getGenerationID();
mIsHardware = mBitmap->isHardware();
mBitmap.reset();
}
因为像素数据是在native heap,所以会在native进程清空数据。注意,这里只是清空像素数据,但是native的bitmap对象并没有释放。
而8.0以下是交由java GC来处理的,因为像素数据是在java heap。
那如果没有调用recycle呢?
这里我网上找了资料,不管是8.0之前还是之后,都会去监听java层的bitmap是否已释放。所以8.0之后,java层的bitmap被释放后,像素数据会被清空,native层的bitmap也会被释放;而8.0之前,java层的bitmap被释放的同时,像素数据就被清空了,接着native层的bitmap也会被释放。
大家不妨去做个试验,在8.0以上的手机,退出页面的时候如果recycle,像素数据占用的内存会被立刻释放;反之则不会,因为需要等到java GC。而8.0以下的手机,无论是否调用recycle,都要等到java GC内存才会被释放。
最终的结论:8.0以上的手机不会因为bitmap而发生oom,但这并不意味着我们就不需要处理bitmap,native heap过大同样会造成手机内存吃紧。我们可以学习glide的处理机制,用bitmapPool来复用bitmap,在onTrimMemory回调中,根据level的不同去回收部分bitmap。
@SuppressLint("InlinedApi")
@Override
public void trimMemory(int level) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "trimMemory, level=" + level);
}
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
trimToSize(getMaxSize() / 2);
}
}
当我们的进程处于后台进程,并且被系统放入了lruList,随时会被回收,这时候就会去清除bitmapPool;当我们的进程按home键切换为不可见或者在前台,系统内存吃紧的最高等级(5,10,15三个等级),释放bitmapPool到一半或以下。
那么释放bitmap的具体操作是怎么样的呢?为了兼容8.0及以上版本,我建议最好手动调用recycle方法,先把像素数据占用的内存立刻释放掉,然后再等GC把java层bitmap和native层bitmap回收。