原文:https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/
在Facebook的Android app上面快速和高效的显示图片十分重要,我们这几年在高效存储图片上遇到很多问题。图片很大,但是设备的内存很小。每一个像素占用4byte数据 ---红,绿,蓝和透明色。如果手机的屏幕是480x800像素,一个全屏的图片会占用1.5M内存。手机只有很小的内存,并且Android设备会把有效的内存分给多个App。在一些设备上面,Facebook app只有很小的16M可用内存---一张图就会占用10%内存!
你的App 内存不足会发生什么呢?会Crash掉。我们打算通过开发一个库(Fresco)去解决这个问题 --- 它会去管理图片以及它占
用的内存。Crash可以滚蛋了。
为了理解Facebook在这个问题上面做了什么,我们需要理解Android上面不同的内存堆的概念。
Java heap是最严格的,每个应用都被手机厂商限制的一块内存区。所有使用Java语言的new操作创建的对象都会放在那里。这是一块相对安全的内存区域。内存是垃圾回收的,所以当App用完内存后,系统会自动回收掉。
不幸的是,进程的垃圾回收机制正式我们的问题所在。为了开垦更多的可用内存,Android在垃圾回收的时候,必须要挂起这个应用,这是App被挂起或者短暂短暂停留的最普遍的原因,让用户在用App的时候感到用户体验不好,他们在滚动或者按下按钮的时候---只能看到应用在响应前莫名其妙的停留一段时间。
比较起来,本地heap是在C++的new操作创建的内存区。这里会有更多的可用内存。这块内存是由设备的物理内存限制。这里没有垃圾回收机制并且没有任何需要显示的地方。然而,C++程序需要负责释放每一byte它创建的内存,否则会造成内存泄漏,最终会crash。
Android还有一块内存区,叫做匿名共享内存区。这个操作更像是本地heap,但是会带来额外的系统调用。Android会取消锁定内存,而不是释放它。这是一块延时释放的内存区;这块内存必须在系统十分缺乏内存的时候,才会释放掉。如果Android "锁定"这块内存时,旧的数据依然不会被释放。
匿名共享内存并不能被Android应用直接访问,但是有少量意外情况,图片则是其中一个。当你创建一个已经解压的图片,被称为Bitmap,Android API允许你像这样指定一个可清理的Bitmap:
BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
可清理的位图是在共享内存区。但是,垃圾回收器不会自动的清理他们。当绘图系统正在渲染这张图片的时候,Android系统库会"锁定"这块内存,完成后“解锁”。内存被解锁后,仍然随时可能会被回收回收掉。如果一张解锁的图片再次被绘制,系统会再快速解码一次。
这看起来像是一个完美的解决方案,但是问题在于快速解码是在UI thread里做的,解码是一种CPU密集型操作,UI会在解码成功之前短暂停顿。就因为这个原因,Google现在不建议使用这个功能。他们现在建议用另外一个标志位: inBitmap。但是这个flag在3.0之前并没有。即使这样,除非所有App的图片大小都是一样,否则也没啥用。这个并不是Facebook的使用场景。在Android4.4,这个限制被移除了,但是我们需要一个解决方案,能够让任何人都能够用上Facebook,包括那些使用Android2.3的人。
我们发现一个方案能让我们拥有快的UI响应和快的内存。如果我们不在UI thread锁定内存,并且确保它不会被解锁,我们就能够把图片保存在匿名共享内存区里面又不影响UI。真幸运能够拥有它。Android DNK有一个叫AndroidBitmap_lockPixels的函数来做这个事。这个函数搬来是原本是用来接着unlockPixels调用,用来重新“解锁”内存。
我们意识到我们的突破点来了。如果我们调用lockPixels,而不调用unlockPixels,我们就会创建一个安全的远离Java heap并且不会减慢UI thread的的图片。少量的C++代码就能实现。
从蜘蛛侠里,我们知道“巨大的责任感迸发出更强的力量”。锁定的purgeable Bitmap不会有Java内存回收,也没有匿名共享内存清理机制来清理,为了防止内存泄漏,我们只能靠自己。
在C++,通常的解决方案是创建智能指针类来实现引用计数功能。把C++的语言特性发挥到极致---拷贝构造函数,运算符重写,deterministic析构函数。但是Java并没有这些特性,垃圾回收机制会处理一切。所以,我们不得不在Java中实现C++风格的方法。
我们创建了两个类来做这个,一个叫做SharedReference. 它有两个方法:addReference, deleteReference。调用者必须要在他们使用底层对象或超出返回的时候调用。一旦引用计数归0,资源清理(比如bitmap.recycle)就会发生。
是的,很明显,让java开发者用这些方法很容易出错。Java是选择用来避免这些的语言。所以,基于SharedReference的上层,我们创建了CloseableReference。它不止是实现了Closeable接口,还实现了Cloneable接口。构造函数和clone()方法会调用addReference(),close()方法会调用deleteReference()。所以,Java开发者只需要以下两点简单的原则:
1、分配CloseableReference到新对象的时候,调用clone()。
2、在超出返回之前,调用close()。通常是在finally块里面。
这些规则有效的防止了内存泄漏,并且能够让我们更加简单的在Java程序里面使用native的内存,比如Facebook for Android和Messager for Android.
public class OutputProducer implements Producer {
private final Producer mInputProducer;
public OutputProducer(Producer inputProducer) {
this.mInputProducer = inputProducer;
}
public void produceResults(Consumer outputConsumer, ProducerContext context) {
Consumer inputConsumer = new InputConsumer(outputConsumer);
mInputProducer.produceResults(inputConsumer, context);
}
private static class InputConsumer implements Consumer {
private final Consumer mOutputConsumer;
public InputConsumer(Consumer outputConsumer) {
mOutputConsumer = outputConsumer;
}
public void onNewResult(I newResult, boolean isLast) {
O output = doActualWork(newResult);
mOutputConsumer.onNewResult(output, isLast);
}
}
}
有时候也想要显示有圆角甚至是圆形的图片,这些所有的操作,都需要是快且平滑的。
我们以前的实现都是用的View对象---当绘画的时候用ImageView交换tuxiang占位符,这个方式会很慢,因为改变View会强制Android去更新整个Layout,用户滚动的时候是不想要发生这个的。更好的方式是使用Android Drawable对象,它能够快速的交换图像占位符。
所以,我们发明了Drawee。这是一个类似于MVC架构的用来显示图像的框架。Model叫做DraweeHierarchy。用来实现层叠式的图像绘制,实现底层的图像成像,层叠,淡入或者缩放功能。
DraweeControllers连接到图像的pipeline。或者是链接到任意的ImageLoader。主要用来操作后台的图像处理。它会去接收pipeLine的事件,并且决定怎么处理这些事件。它们会去控制DraweeHierarchy到底显示什么,比如占位符、错误符号、或下好的图片。
DraweeViews只有几个功能,但是这些功能都是决定性的。他们主要监听Android的系统消息,决定图像是否继续显示在屏幕上面。当离开屏幕的时候,DraweeView会通知DraweeControllers回收图像的资源,避免内存泄漏。此外,DraweeControllers还会通知image pipeline在图片不需要显示的时候,取消下载的请求。因此,像Facebook这样滑动大量的图片列表的情况下,并不会中断网络。
综上所述,显示图片的繁杂的工作没那么困难了,在代码里调用DraweeView,指定一个URI,指定一些其他可选参数,其他事情都是自动化的了。开发者不用再担心图像内存管理和图片显示了。所有的东西都会在类库里面做好了。
已经创造了这么强有力的用来管理和展示图片的工具集,我们想要共享到Android社区里面去,我们很高兴的宣布,这个项目现在已经开源了!
Fresco是已经流传了几个世纪的绘画方式,我们很荣幸很多伟大的艺术家用过这种方式,包括意大利的拉斐尔,斯里兰卡的锡吉里亚古宫等艺术家。我们达不到那样的水平。但是我们希望Androidapp开发者们能够享受到使用类库带来的乐趣。