您的位置:首页 > 移动开发 > Android开发

Fresco简介:一个新的Android图片加载库

2016-04-05 17:22 603 查看
高效的显示图片对于Android应用而言十分重要。然而过去的几年间我们在有效的存储图片这一问题上遇到了许多挑战。图片太大,“设备”太小。一个像素需要占用4个字节(红、绿、蓝和透明度)。一部400*800的手机,显示一张全屏的图片需要使用1.5MB的内存。手机本来就没多少内存,Android系统还将这些内存分给了多个应用使用。在一些设备上,一个应用程序仅能使用16MB的内存——一张图片就用完了。

应用程序用光了内存会导致什么?崩溃啊!为了解决这一问题,我们写了一个新的图片库——Fresco,它能有效的管理图片及其所占用的内存。让讨厌的崩溃问题见鬼去吧~

内存区域分类

为了理解我们究竟要做什么,我们需要先掌握一下Android设备中的几种可用内存区域。

Java heap是一种被严格限制大小的内存块。所有通过关键字 new 创建的对象都会被分配在这里。这一块内存区域相对安全。由于有垃圾回收(gc)机制,当应用程序内存不够时,系统会对该区域自动的进行回收。

不幸的是,垃圾回收的过程本身就是一个问题。为了进行垃圾回收,Android需要停止应用程序的进行。这正是最为常见的ANR(Application not response)现象的起因。

相对的,native heap 是一种被C++中的new操作符操作的内存区域。这块区域的内存相对较多。其大小只与设备的实际物理大小有关(不是由Android系统限制)。没有垃圾回收机制同时不会导致ANR问题。然而,C++程序员需要对其所分配的每个字节负责,如果不及时释放的话将导致可用内存减少并最终导致崩溃。

除了上述两块内存区域,Android还有一块名为ashmem的内存区域。其操作类似与native heap,但是需要额外的系统调用。Android可以“解绑”(unpin)这块区域而不是释放,这是一种懒惰的释放方式;只有在需要更多的内存区域时才会正在的释放。当Android“绑定”(pin)这块内存区域的内存时,之前的数据仍然存在如果没被释放的话。

可清除的位图

Ashmem无法直接被Android应用程序访问,然而图片数据除外。当你创建一个解码的图片,即bitmap时,Android API允许你将该图片定义为可清除的:

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


可清除的位图分配在ashmem。然而,垃圾回收器将不会自动的对这一区域进行回收。Android系统类库将会在绘制系统渲染一张图片的时候“绑定”这块内存区域,在渲染结束后将会“解绑”。被“解绑”的内存块可以在任何时刻被系统回收。如果一张被“解绑”的图片需要再次被绘制,系统将会飞速的重新解码。

看起来这个机制是完美的解决办法,但是问题在于这一飞速的解码过程是在UI线程执行的。解码是一个CPU密集的操作,所以解码的同时UI线程将会停滞。基于这个原因,Google并不推荐使用这个特性。他们推荐使用另外一个标志,imBitmap。然而,这个标志位在Android3.0以前的版本是不存在的。并且,在Android4.4以前这个机制仅对同样尺寸的图片有效。因此,我们急需一个通用的解决方法。

自己动手,丰衣足食

我们找到了一个两全其美的办法——快速的UI绘制,高效的内存使用。如果我们在提前绑定ashmem,并且保证这块内存区域不被释放,那么我们就能够将图片保存在ashmem同时UI线程不被阻塞。幸运的是,NDK中提供了一个函数实现这一机制——AndroidBitmap_lockPixels。这个函数本应和unlockPixels函数成对使用。

突破点来了,我们意识到如果我们仅调用lock函数而不调用匹配的unlock函数,那么一切问题迎刃而解。我们创建了一个位图安全的存在于java heap之外,同时不会降低UI的效率。再写几行C++代码,我们就可以回家睡觉了(大晚上的还在加班,看来facebook的同行们也不轻松啊- -!)

以C++的思维书写Java代码

正如蜘蛛侠说说,“能力越大责任越大”。被绑定后的可清除位图内存块即不能通过垃圾回收进行回收,又不能通过ashmem构建的回收机制回收。这个时候只能我们自己上了。

在C++中,通常的做法是构建实现了引用计数的智能指针对象。这一机制使用了C++语言的特性——拷贝构造,赋值运算符和析构函数。这些语法Java里压根没有。(我大Java有垃圾回收好伐)。所以我们必须在Java里写一些C++风格的代码了。

我们写了两个类来完成这个操作。一个叫做SharedReference。该类含有两个方法addReference和deleteReference。这两个方法分别在获取到对象引用和离开其作用域时使用。一旦引用计数器归零,资源释放函数被调用。

显然,让Java程序员去写这种代码要被喷死不可。本来Java这门语音就是用来避免这类操作的。所以在SharedReference的基础上,我们构建了CloseableReference。其实现了Closeable接口和Cloneable接口。在该类的构造函数中和clone()函数中,我们会调用addReference(),在close()函数中我们调用了deleteReference()。因此Java程序员仅需要遵循以下两个原则进行编码:

1.当将一个CloseableReference的对象分配给另外一个对象时,调用clone函数。

2.当离开作用域时调用close函数。

这些原则高效的避免了内存泄漏。并且让我们成功的在Android设备上使用了ashmem。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: