Fresco是Facebook出品的高性能图片加载库,号称在所有图片加载库中是效率、性能最好的。Fresco整个库还挺大的,还有native层。这里不对Fresco做深入分析,只关注Fresco在Android Bitmap的管理上采用了哪些黑科技

Android的内存区域

Java Heap(Dalvik Heap),这部分的内存区域是由Dalvik虚拟机管理,通过Java中new关键字来申请一块新内存。这块区域的内存是由GC直接管理,能够自动回收内存。这块内存的大小会受到系统限制,当内存超过APP最大可用内存时会OOM

Native Heap,这部分内存区域是在C++中申请的,它不受限于APP的最大可用内存限制,而只是受限于设备的物理可用内存限制。它的缺点在于没有自动回收机制,只能通过C++语法来释放申请的内存

Ashmem(Android匿名共享内存),这部分内存类似于Native内存区,但是它是受Android系统底层管理的,当Android系统在内存不足时,会回收Ashmem区域中状态是unpin的对象内存块,如果不希望对象被回收,可以通过pin来保护一个对象

Purgeable Bitmap

Ashmem一般在应用层中是无法直接访问的,除了几个特例之外。其中之一就是decode bitmap,我们可以通过设置BitmapFactory.Optinons.inPurgeable = true来创建一个Purgeable Bitmap,这样decode出来的bitmap是在Ashmem内存中,GC无法自动回收它。当该Bitmap在被使用时会被pin住,使用完之后就unpin,这样系统就可以在将来某一时间释放这部分内存。

如果一个unpinned的bitmap在之后又要被使用,系统会运行时又将它重新decode。为了redecode bitmap,系统需要获得bitmap的编码数据,默认情况下系统会将bitmap的InputStream拷贝一份保存起来,待下次redecode时使用。但是这个decode操作是发生在UI线程中的有可能会造成掉帧现象,因此改做法已经被Google废弃掉,转为鼓励使用inBitmap来告知bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。

但是使用inBitmap需要注意几个限制条件:

  • 在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。
  • 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。 我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。

Pin Bitmap

为了让inPurgeable的bitmap不被自动unpinned,可以通过使用jni函数AndroidBitmap_lockPixels()函数来强制pin bitmap,这样我们就可以在bitmap被使用时不会被系统自动unpinned,从而也就避免了unpinned的bitmap在重新被使用时又会被重新decode而引起的掉帧问题。同样的,Android也提供了AndroidBitmap_unlockPixels()来让bitmap重新变为unpinned状态,这样系统在内存不足时就可自动回收这部分内存

参考文献

Facebook tricks for image handling in Android
Introducing Fresco: A new image library for Android
Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

欢迎大家拍砖

原创不易,欢迎转载,但还请注明出处:waynell.github.io