在Facebook的Android客户端上快速高效的显示图片是非常重要的。然而多年来,我们遇到了很多如何高效存储图片的问题。图片太大,而设备太小。一个像素点就占据了4个字节数据(分别代表R G B和alpha)。如果在一个480*800尺寸的手机屏幕上,一张单独的全屏图片就会占据1.5MB的内存空间。通常手机的内存都非常小,而这些内存被多种多样的app划分占用。在一些设备上,Facebook app虽然只有16MB,但是仅仅一个图片就占用了1/10的空间。

  当你的app用完你的内存时会发生什么呢?会崩溃。我们着手通过创建一个类(Fresco)来解决这个问题,它会管理好图片和内存。崩溃走开!

内存区域

  为了了解Facebook所做的工作,我们必须理解应用于Android中的各种堆内存。

  Java heap能严格的限制每个应用,它由设备制作商设置的。所有由Java语言的new操作符创建的对象都会来到这里。这里是一个相对安全的内存区域。内存总会被回收的,所以当app已经用完内存时,系统将会自动回收它。

  不幸的是,内存回收阶段出现了问题。为了回收内存,Android必须停止app的运行,然后运行垃圾回收器。这是你正在使用中的app出现停止或变慢的原因之一。这令人沮丧,或许用户正尝试滚动或者按一个button,结果app没有响应,只有莫名的等待。

  相反,native heap是被C++ new操作符使用的一个堆。在这里有大量的可用内存。app被限制在设备的可用物理内存中,这里不存在垃圾回收,app也不会变慢。然而,我们需要在C++程序中释放内存空间,否则他们会内存溢出,app最终会崩溃。

  Android中还有另外一块内存区域,叫ashmem。这很像是本地heap,但是需要额外的系统调用。Android能够“unpin”内存而不是释放他们。这是一种懒释放,只有在系统真正需要更多内存空间的时候才会释放。当Android再次指向(pin)内存时,旧的数据还会在那里,只要没有被释放的话。

可清除的位图

  ashmem不是直接的访问java应用,因为存在一些异常,图像就是其中一个。当你创建一个解码的(未压缩的)图像时,比如一个bitmap,Android API会允许你指定这个图像为“可清除的”: 

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

这些可清除图像存在于ashmem中,然而,垃圾回收器并不会自动的回收他们。当绘制系统渲染图像时,Android的系统库会标记这块内存,当渲染完毕后,便不再标记它。没有被标记的内存随时都能被系统回收。如果一个没有被标记的图像需要重新绘制,系统会重新解码图像。

  这听起来像是一个完美的解决方案,但问题是快速解码图像发生在UI线程。解码是一个CPU密集型操作,执行时UI会变慢。为此,Google反对使用特征,他们推荐使用一种不同的flag:inBitmap。然而这种flag在Android3.0才出现。直到现在,这种flag也不常用,除非app里面所有图像的尺寸相同,这完全不符合Facebook的情况。这种限制直到Android4.4才移除。然而我们需要一个能让所有Facebook用户满意的方案,包括使用Android2.3的那些用户。

拥有蛋糕并吃掉它

  我们发现一种能够兼顾快速UI和快速内存的解决方案。如果我们提前标记内存,在非UI线程,并且保证不被除掉标记,然后我们能够保证图片存在于ashmem中而不会导致UI变慢。运气好的话,NDK中有一个函数能准确的做到,叫“AndroidBitmap_lockPixels”。这个函数会在“unlockPixels”调用之后被调用,再次去掉内存标记。

  当我们意识到我们不必那样做时,我们取得了突破。如果我们没有匹配“unlockPixels”而调用“lockPixels”,我们创建了一种安全存在于Java heap之外的图像,还不会拖慢UI线程。几行C++代码即可办到的事情。

用Java写代码,但用C++思考

  正如我们从蜘蛛侠上所学到的,“巨大的力量来源于重大的责任”。标记的可清除图像既不是垃圾回收也不是ashmem中的内置清除工具来防止内存泄漏。我们相信我们自己。

  在C++中,通常的办法是创建漂亮的指针类来实现引用计数,这些利用C++中的一些工具,如copy constructors, assignment operators, and deterministic destructors。这些动态特性不存在于Java中,在Java中,垃圾回收器会处理一切。所以我们必须在Java中找到实现C++风格的方法。

  我们利用两个类来实现之,一个是SharedReference,它有两个方法,addReference和deleteReference。每当他们获取底层对象或超出作用域时调用者必须调用他们。一旦引用计数器归零时,资源清理(如Bitmap.recycle)发生。

  然而显而易见的是,对于Java开发者来说,调用这些方法及其容易出错。Java被选为一个避免出现此种情况的语言。所以在SharedReference的顶部,我们建立了CloseableReference。它不但实现了Java Closeable接口,同时也实现了Cloneable。构造器和clone()方法调用addReference(),而且close()方法调用deleteReference。所以Java开发者仅需要遵循两个简单的规则。

  1.分配一个CloseableReference给一个新对象时,调用.clone()方法。

  2.超出作用域之前,调用.close()方法,通常在一个finally块中。

  这些规则在防止内存泄漏方面是非常有效的,同时让我们享受到本地内存管理的乐趣,比如在Facebook for Android 或 Messenger for Android这样的比较大的Java应用中。

它不只是加载器--它是管道

当在移动设备上面显示一个图像时,会有很多步骤。

  有几个很棒的开源库会进行这些步骤,如Picasso,Universal Image Loader,和Volley等等,不一而足。这些都对Android发展做出了重要贡献。我们相信我们的开源库在几个重要的方面走的更远。

  考虑到这些步骤作为一个管道而不是一个加载器是有区别的,每一步都应该尽量独立于其它部分,输入一些参数同时输出结果。它应尽可能并行的进行一些操作。而其他部分则串行。一些执行仅在特定条件下。他们执行的一些线程具有特殊的需求。此外,如果我们看重创新的图像,整个图片会变得更复杂。很多人是在非常慢的网络连接中使用Facebook的,我们想让这些用户能够快速的看到他们的图片,甚至是在图片真正加载完毕之前。

别担心,爱上流吧

  传统上,Java中的异步代码是通过像Future这样的机制被执行的。代码上传后在另外一个线程执行,像一个Future对象能被检测到结果是否准备好。然而,假设仅有一个结果,当处理先进的图像时,我们想,会出现一个连续结果的整体序列。

  我们的解决方案是Future的一个大概版本,叫做DataSource。他提供一个订阅方法,调用者必须传递一个数据订阅者和一个执行器。数据订阅者收到来自中间的或最终结果的DataSource的通知,而且提供一个简单方法来区分他们。因为我们经常处理一些需要明确调用close的对象,DataSource本身也是一个Closeable。

  在幕后,上面方框内的每一个都被实现了,并且使用了一个新的框架,叫做Producer/Consumer。这里我们画出了来自ReactiveX框架的灵感。我们的系统有与RxJava相同的接口,而且更适合移动设备和支持Closeable的嵌入式。

  接口很简单,Producer有一个独立的方法,是produceResults,它负责得到一个Consumer对象。相反,Consumer有一个onNewResult方法。

  我们使用这样的一个系统来把生产者链接在一起。假设我们有一个producer,它的工作是将类型I转换成类型O,那么代码就是如下所示。

public class OutputProducer<I, O> implements Producer<O> {

  private final Producer<I> mInputProducer;

  public OutputProducer(Producer<I> inputProducer) {
this.mInputProducer = inputProducer;
} public void produceResults(Consumer<O> outputConsumer, ProducerContext context) {
Consumer<I> inputConsumer = new InputConsumer(outputConsumer);
mInputProducer.produceResults(inputConsumer, context);
} private static class InputConsumer implements Consumer<I> {
private final Consumer<O> mOutputConsumer; public InputConsumer(Consumer<O> outputConsumer) {
mOutputConsumer = outputConsumer;
} public void onNewResult(I newResult, boolean isLast) {
O output = doActualWork(newResult);
mOutputConsumer.onNewResult(output, isLast);
}
}
}

  这让我们将一系列复杂的步骤链接在一起,而且仍然保持他们逻辑上是独立的。

动画--从一个到多个

  Stickers,是存储在GIF中的WebP格式的动画,深受Facebook用户喜欢。但支持Stickers出现了新的挑战。一个动画并不是一幅图像而是一系列的图像的组合,其中每一张图片都要被解码,并存入内存,展示出来。存储大动画中的每个单独帧到内存是不合理的。

  我们创建了AnimatedDrawable,一个具有渲染能力的类,有两个后端--一个是GIF,另一个是WebP。AnimatedDrawable实现了标准Android的Animatable接口,所以调用者能够随时开始和结束动画。为了优化内存存储,在内存足够时我们缓存了所有帧到内存中,但是帧太多时,我们会匆忙的解码。这种行为是完全可以被调用者调节的。

  两个后端都是用C++代码实现,我们拷贝已编码数据和已解析的元数据,如宽和高。我们引用计数数据,这样可以在Java端让多个Drawable同时访问一个单独WebP图像。

我如何爱你?让我制定方法

  当图片正在从网络被下载时,我们想显示一个占位符。如果下载失败,我们显示一个错误指示器。当图片到达时,我们做出一个快速的淡入效果动画。我们经常缩放图片,或者应用一个显示矩阵,使用硬件加速器来渲染它。而且我们不总是缩放图片--有用的焦点可能是其他地方。有时我们想显示一个圆角图片,或者圆形图片。所有这样的操作都应该是快速而平滑的。

  我们以前的实现都是使用Android的View对象--当时间到时为一个ImageView交换占位符,这会非常缓慢的。切换视图会强迫Android去执行整个layout,并不是像用户滚动这样的事情。一个更明智的方法是使用Android的Drawables,它能被快速交换。

  所以我们创建了Drawee。这是一个为显示图像的类似于MVC的框架。此模型被称作DraweeHierarchy。它是一个Drawables的实现层次架构,其中每个应用一个特定的函数--成像、分层、淡入、或缩放--对于底层图像来说。

  DraweeControllers连接图像管道--或对于任何图像加载器--照顾到后台图像控制。他们接收来自管道的事件,并决定如何处理它们。他们控制DraweeHierarchy的实际显示--如占位符、错误符号、或完成图片。

  DraweeViews仅有几个功能,但都是起决定作用的。他们监听Android视图不再显示的系统事件。当离开屏幕,DraweeView能告诉DraweeController关闭图像使用的资源。这会避免内存泄漏。另外,如果还没有出去的话,控制器将会告诉图像管道取消网络请求。因此,滚动长列表图像(如在Facebook中经常这样),并不会中止网络。

  有了这些工具,显示图像就不那么困难了。调用程序只需要实例化一个DraweeView,指定一个URI,然后随意设置一些其他的参数。其他所有工作都是自动化的。开发者不必担心管理图像内存或者更新流到图像。所有事情都是靠类库实现。

Fresco

  已经建立了灵活的工具集来显示和操作图像,我们想分享它给Android开发者社区。我们很高兴的宣布,从今日开始,这个项目开放源代码。

  fresco(湿壁画)是一种绘画技术,它已经流行了几个世纪。我们很荣幸许多伟大的艺术家都曾使用过这种形式,从意大利的文艺复兴大师如拉斐尔,到斯里兰卡的锡吉里亚古宫艺术家们。我们不自称达到那样的水平。我们希望Android app开发者们能够享受到使用类库带来的乐趣。

本文来自:https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/

(Facebook开源项目)Fresco:一个新的Android图像处理类库的更多相关文章

  1. Facebook开源项目:我们为什么要用Fresco框架?

    (Facebook开源项目)Fresco:一个新的Android图像处理类库 在Facebook的Android客户端上快速高效的显示图片是非常重要的.然而多年来,我们遇到了很多如何高效存储图片的问题 ...

  2. Android Studio复制项目作为一个新的工程

    Android Studio复制项目作为一个新的工程 等待..... 好了 可能会安装失败 Failed to finalize session : INSTALL_FAILED_INVALID_AP ...

  3. 一个新的Android Studio 2.3.3可以在稳定的频道中使用。A new Android Studio 2.3.3 is available in the stable channel.

    作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313134555 @qq.com 一个新的Android Studio 2.3 ...

  4. facebook开源项目集合

    Facebook的开源大手笔   1. 开源Facebook平台代码 Facebook在2008年选择将该平台上的重要部分的代码和应用工具开源.Facebook称,平台已经基本发展成熟,此举可以让开发 ...

  5. 跟我一起使用webpack给一个开源项目添加一个运行入口

    啦啦啦啦啦不要把webpack想的很高大上就放弃了探究的想法,其实webpack特别的平易近人,就是一个工具 今天看到了一个超级美丽的项目 你可以看到各种各样的口红色号,满屏的粉色,哇哇哇哇塞,美美哒 ...

  6. [开源项目]Shell4Win,一个在Windows下执行shell命令的解释器

    背景 顺利拿到心目中的理想offer之后,心里的负担一下减轻了很多,希望利用还没毕业之前这段难得的悠闲时间做一点有意义的事情.于是希望能做一个长久以来都想做的开源项目,就是题中提到的Windows下的 ...

  7. Android4.0 添加一个新的Android 键值

    这里添加新的键值,不是毫无凭据凭空创造的一个键值,而是根据kernel中检测到的按键值,然后转化为Android所需要的数值: 以添加一个Linux键值为217,把它映射为android的键值Brow ...

  8. 【开源项目12】Retrofit – Java(Android) 的REST 接口封装类库

    Retrofit官网:http://square.github.io/retrofit/ Retrofit 和Java领域的ORM概念类似, ORM把结构化数据转换为Java对象,而Retrofit ...

  9. 开源项目Universal Image Loader for Android 说明文档 (1) 简介

     When developing applications for Android, one often facesthe problem of displaying some graphical ...

随机推荐

  1. 移动HTML5前端性能优化总结

    概述 1. PC优化手段在Mobile侧同样适用 2. 在Mobile侧我们提出三秒种渲染完成首屏指标 3. 基于第二点,首屏加载3秒完成或使用Loading 4. 基于联通3G网络平均338KB/s ...

  2. PHP面向对象(OOP)----分页类

    > 同验证码类,分页也是在个人博客,论坛等网站中不可缺少的方式,通过分页可以在一个界面展示固定条数的数据,而不至于将所有数据全部罗列到一起,实现分页的原理其实就是对数据库查询输出加了一个limi ...

  3. 理解redis高可用方案

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  4. Humble Numbers(丑数) 超详解!

    给定一个素数集合 S = { p[1],p[2],...,p[k] },大于 1 且素因子都属于 S 的数我们成为丑数(Humble Numbers or Ugly Numbers),记第 n 大的丑 ...

  5. 一个初学所了解的jquery事件

    在jquery中,事件总体分为两大类:简单是件和复合事件今天我们只来学习一下简单事件 绑定事件和事件处理函数的语法格式(DOM 对象.事件名=函数:) 简单事件分为: 1.window事件 所谓win ...

  6. ios 相机调用之读取相册

    UIIamgePickerControllerr可以从照片库中读取一张图片到咱们应用程序中来   步骤:   //创建图片判断图片库是否可以使用   if([UIImagePickerControll ...

  7. IBatis入门

    iBatis 简介: iBatis 是apache 的一个开源项目,一个O/R Mapping 解决方案,iBatis 最大的特点就是小巧,上手很快.如果不需要太多复杂的功能,iBatis 是能够满足 ...

  8. TCP/IP协议(零)TCP/IP参考模型

    我们先浏览一下TCP/IP的参考模型,对网络模型有一个大致的了解,后续着重学习OSI参考模型. TCP/IP参考模型是计算机网络的祖父ARPANET和其后继的因特网使用的参考模型. 1.结构 TCP/ ...

  9. Struts2之环境配置

    在学习struts2之前,首先我们要明白使用struts2的目的是什么?它能给我们带来什么样的好处? 设计目标 Struts设计的第一目标就是使MVC模式应用于web程序设计.在这儿MVC模式的好处就 ...

  10. 一个简单的php站点配置

    一个简单的php站点配置   现在我们来看在一个典型的,简单的PHP站点中,nginx怎样为一个请求选择location来处理:   server {     listen      80;     ...