Android Bitmap(位图)详解

1、背景android

在Android开发中,任何一个APP都离不开图片的加载和显示问题。这里的图片来源分为三种:项目图片资源文件(通常为res/drawable目录下的图片文件)、手机本地图片文件、网络图片资源等。图片的显示咱们通常采用ImageView做为载体,经过ImageView的相应API便可设置其显示的图片内容。c++

咱们知道:若是是须要展现项目中的图片资源文件,咱们只须要调用ImageView的setImageResource(int id)方法并传入该图片资源的id(通常为R.drawable.xxx)便可。可是若是是须要展现手机本地的某张图片或者网络上的某个图片资源,又该怎么办呢?——问题Agit

为了回答问题A,咱们先思考一个更深的问题B:Android中是如何将某一张图片的内容加载到内存中继而由ImageView显示的呢?github

咱们知道:若是咱们想经过TextView展现一个本地txt文件的内容,咱们只须要由该文件建立并包装一个输入流对象。经过该输入流对象便可获得一个表明该文件内容的字符串对象,再将该字符串对象交由TextView展现便可。换句话说,这个txt文件的内容在内存中的表达形式就是这个字符串对象。web

类推一下,虽然图片文件也是文件,可是咱们显然不可能对图片文件也采用这种方式:即经过该图片创建并包装一个输入流对象再获取一个字符串对象。毕竟不管如何咱们都没法将某个图片的内容表示为一个字符串对象(细想一下就知道了,你能经过一段话100%准确地描述一张图片吗?显然不现实)。那么,这就引入了问题C:既然字符串对象不行,那么咱们该以哪一种对象来在内存中表示某个图片的内容呢?答案就是:Bitmap对象!算法

2、基本概述缓存

Bitmap,即位图。它本质上就是一张图片的内容在内存中的表达形式。那么,Bitmap是经过什么方式表示一张图片的内容呢?安全

Bitmap原理:从纯数学的角度,任何一个面都由无数个点组成。可是对于图片而言,咱们不必用无数个点来表示这个图片,毕竟单独一个微小的点人类肉眼是看不清的。换句话说,因为人类肉眼的能力有限,咱们只须要将一张图片表示为 有限但足够多的点便可。点的数量不能无限,由于无限的点信息量太大没法存储;可是点的数量也必须足够多,不然视觉上没法造成连贯性。这里的点就是像素。好比说,某个1080*640的图片,这里的像素总数即为1080X640个。性能优化

将图片内容表示为有限但足够多的像素的集合,这个“无限→有限”的思想极其迷人。因此,咱们只须要将每一个像素的信息存储起来,就意味着将整个图片的内容进行了表达。微信

像素信息:每一个像素的信息,无非就是ARGB四个通道的值。其中,A表明透明度,RGB表明红绿蓝三种颜色通道值。每一个通道的值范围在0~255之间,即有256个值,恰好能够经过一个字节(8bit)进行表示。因此,每一个通道值由一个字节表示,四个字节表示一个像素信息,这彷佛是最好的像素信息表示方案。

可是这里忽略了两个现实的需求问题:

①在实际需求中,咱们真的须要这么多数量的颜色吗?上述方案是256X256X256种。有的时候,咱们并不须要这么丰富的颜色数量,因此能够适当减小表示每一个颜色通道的bit位数。这么作的好处是节省空间。也就是说,每一个颜色通道都采用8bit来表示是表明所有颜色值的集合;而咱们能够采用少于8bit的表示方式,尽管这会缺失一部分颜色值,可是只要颜色够用便可,而且这还能够节省内存空间。

②咱们真的须要透明度值吗?若是咱们须要某个图片做为背景或者图标,这个图片透明度A通道值是必要的。可是若是咱们只是普通的图片展现,好比拍摄的照片,透明度值毫无心义。细想一下,你但愿你手机自拍的照片透明或者半透明吗?hell no! 所以,透明度这个通道值是否有必要表示也是根据需求自由变化的。

具体每一个像素点存储ARGB值的方案介绍,后面会详细介绍。

总结:Bitmap对象本质是一张图片的内容在内存中的表达形式。它将图片的内容看作是由存储数据的有限个像素点组成;每一个像素点存储该像素点位置的ARGB值。每一个像素点的ARGB值肯定下来,这张图片的内容就相应地肯定下来了。

如今回答一下问题A和问题B:Android就是将全部的图片资源(不管是何种来源)的内容以Bitmap对象的形式加载到内存中,再经过ImageView的setImageBitmap(Bitmap b)方法便可展现该Bitmap对象所表示的图片内容。

3、详细介绍

一、Bitmap.Config

Config是Bitmap的一个枚举内部类,它表示的就是每一个像素点对ARGB通道值的存储方案。取值有如下四种:

ARGB_8888:这种方案就是上面所说的每一个通道值采8bit来表示,每一个像素点须要4字节的内存空间来存储数据。该方案图片质量是最高的,可是占用的内存也是最大的

ARGB_4444:这种方案每一个通道都是4位,每一个像素占用2个字节,图片的失真比较严重。通常不用这种方案。

RGB_565:这种方案RGB通道值分别占五、六、5位,可是没有存储A通道值,因此不支持透明度。每一个像素点占用2字节,是ARGB_8888方案的一半。

ALPHA_8:这种方案不支持颜色值,只存储透明度A通道值,使用场景特殊,好比设置遮盖效果等。

比较分析:通常咱们在ARGB_8888方式和RGB_565方式中进行选取:不须要设置透明度时,好比拍摄的照片等,RGB_565是个节省内存空间的不错的选择;既要设置透明度,对图片质量要求又高,就用ARGB_8888。

 

二、Bitmap的压缩存储

Bitmap是图片内容在内存中的表示形式,那么若是想要将Bitmap对象进行持久化存储为一张本地图片,须要对Bitmap对象表示的内容进行压缩存储。根据不一样的压缩算法能够获得不一样的图片压缩格式(简称为图片格式),好比GIF、JPEG、BMP、PNG和WebP等。这些图片的(压缩)格式能够经过图片文件的后缀名看出。

换句话说:Bitmap是图片在内存中的表示,GIF、JPEG、BMP、PNG和WebP等格式图片是持久化存储后的图片。内存中的Bitmap到磁盘上的GIF、JPEG、BMP、PNG和WebP等格式图片通过了”压缩”过程,磁盘上的GIF、JPEG、BMP、PNG和WebP等格式图片到内存中的Bitmap通过了“解压缩”的过程。

那么,为何不直接将Bitmap对象进行持久化存储而是要对Bitmap对象进行压缩存储呢?这么作依据的思想是:当图片持久化保存在磁盘上时,咱们应该尽量以最小的体积来保存同一张图片的内容,这样有利于节省磁盘空间;而当图片加载到内存中以显示的时候,应该将磁盘上压缩存储的图片内容完整地展开。前者即为压缩过程,目的是节省磁盘空间;后者即为解压缩过程,目的是在内存中展现图片的完整内容。

 

三、有损压缩和无损压缩

Bitmap压缩存储时的算法有不少种,可是总体可分为两类:有损压缩和无损压缩。

①有损压缩
有损压缩的基本依据是:人的眼睛对光线的敏感度远高于对颜色的敏感度,光线对景物的做用比颜色的做用更为重要。有损压缩的原理是:保持颜色的逐渐变化,删除图像中颜色的忽然变化。生物学中的大量实验证实,人类大脑会自发地利用与附近最接近的颜色来填补所丢失的颜色。有损压缩的具体实现方法就是删除图像中景物边缘的某些颜色部分。当在屏幕上看这幅图时,大脑会利用在景物上看到的颜色填补所丢失的颜色部分。利用有损压缩技术,某些数据被有意地删除了,而且在图片从新加载至内存中时这些数据也不会还原,所以被称为是“有损”的。有损压缩技术能够灵活地设置压缩率。
无能否认,利用有损压缩技术能够在位图持久化存储的过程当中大大地压缩图片的存储大小,可是会影响图像质量,这一点在压缩率很高时尤为明显。因此须要选择恰当的压缩率。

②无损压缩
无损压缩的基本原理是:相同的颜色信息只需保存一次。具体过程是:首先会肯定图像中哪些区域是相同的,哪些是不一样的。包括了重复数据的区域就能够被压缩,只须要记录该区域的起始点便可。
从本质上看,无损压缩的方法经过删除一些重复数据,也能在位图持久化存储的过程当中减小要在磁盘上保存的图片大小。可是,若是将该图片从新读取到内存中,重复数据会被还原。所以,无损压缩的方法并不能减小图片的内存占用量,若是要减小图片占用内存的容量,就必须使用有损压缩方法。
无损压缩方法的优势是可以比较好地保存图像的质量,可是相对来讲这种方法的压缩率比较低。
对比分析:有损压缩压缩率高并且能够灵活设置压缩率,而且删除的数据不可还原,所以能够减小图片的内存占用,可是对图片质量会有必定程度的影响;无损压缩能够很好地保存图片质量,也能保证必定的压缩率虽然没有有损压缩那么高,而且无损压缩删除的数据在从新加载至内存时会被还原,所以不能够减小图片的内存占用。
 
四、位深与色深
咱们知道了图片在内存中和在磁盘上的两种不一样的表示形式:前者为Bitmap,后者为各类压缩格式。这里介绍一下位深与色深的概念:
①色深
色深指的是每个像素点用多少bit来存储ARGB值,属于图片自身的一种属性。色深能够用来衡量一张图片的色彩处理能力(即色彩丰富程度)。
典型的色深是8-bit、16-bit、24-bit和32-bit等。
上述的Bitmap.Config参数的值指的就是色深。好比ARGB_8888方式的色深为32位,RGB_565方式的色深是16位。
 
②位深
位深指的是在对Bitmap进行压缩存储时存储每一个像素所用的bit数,主要用于存储。因为是“压缩”存储,因此位深通常小于或等于色深 。
举个例子:某张图片100像素*100像素 色深32位(ARGB_8888),保存时位深度为24位,那么: 
该图片在内存中所占大小为:100 * 100 * (32 / 8) Byte 
在文件中所占大小为 100 * 100 * ( 24/ 8 ) * 压缩率 Byte
 
五、常见的压缩格式
Bitmap的压缩格式就是最终持久化存储获得的图片格式,通常由后缀名便可看出该图片采用了何种压缩方式。不一样的压缩方式的压缩算法不同。常见的主要有:
①Gif 
Gif是一种基于LZW算法的无损压缩格式,其压缩率通常在50%左右。Gif可插入多帧,从而实现动画效果。所以Gif图片分为静态GIF和动画GIF两种GIF格式。
因为Gif以8位颜色压缩存储单个位图,因此它最多只能用256种颜色来表现物体,对于色彩复杂的物体它就力不从心了。所以Gif不适合用于色彩很是丰富的图片的压缩存储,好比拍摄的真彩图片等。
 
②BMP
BMP是标准图形格式,它是包括Windows在内多种操做系统图像展示的终极形式。其本质就是Bitmap对象直接持久化保存的位图文件格式,因为没有进行压缩存储,所以体积很是大,故而不适合在网络上传输。同时也是由于这种格式是对Bitmap对象的直接存储而没有进行压缩,所以咱们在讨论压缩格式时每每忽略这一种。
 
③PNG
PNG格式自己的设计目的是替代GIF格式,因此它与GIF 有更多类似的地方。PNG格式也属于无损压缩,其位深为32位,也就是说它支持全部的颜色类型。
一样是无损压缩,PNG的压缩率高于Gif格式,并且PNG支持的颜色数量也远高于Gif,所以:若是是对静态图片进行无损压缩,优先使用PNG取代Gif,由于PNG压缩率高、色彩好;可是PNG不支持动画效果。因此Gif仍然有用武之地。
PNG缺点是:因为是无损压缩,所以PNG文件的体积每每比较大。若是在项目中多处使用PNG图片文件,那么在APP瘦身时须要对PNG文件进行优化以减小APP体积大小。具体作法后面会详细介绍。
 
④JPEG
JPEG是一种有损压缩格式,JPEG图片以24位颜色压缩存储单个位图。也就是说,JPEG不支持透明通道。JPEG也不支持多帧动画。
由于是有损压缩,因此须要注意控制压缩率以避免图片质量太差。
JPG和JPEG没有区别,全名、正式扩展名是JPEG。但因DOS、Windows95等早期系统采用的8.3命名规则只支持最长3字符的扩展名,为了兼容采用了.jpg。也因历史习惯和兼容性的考虑,.jpg目前更流行。
JPEG2000做为JPEG的升级版,其压缩率比JPEG高约30%左右,同时支持有损和无损压缩。JPEG2000格式有一个极其重要的特征在于它能实现渐进传输,即先传输图像的轮廓,而后逐步传输数据,不断提升图像质量,让图像由朦胧到清晰显示。此外,JPEG2000还支持所谓的“感兴趣区域”特性,也就是能够任意指定影像上感兴趣区域的压缩质量;另外,JPEG2000还能够选择指定的部分先解压缩来加载到内存中。JPEG2000和JPEG相比优点明显,且向下兼容,所以可取代传统的JPEG格式。
 
 ⑤WebP
WebP 是 Google 在 2010 年发布的图片格式,但愿以更高的压缩率替代 JPEG。它用 VP8 视频帧内编码做为其算法基础,取得了不错的压缩效果。WebP支持有损和无损压缩、支持完整的透明通道、也支持多帧动画,而且没有版权问题,是一种很是理想的图片格式。WebP支持动图,基本取代gif。
WebP不只集成了PNG、JPEG和Gif的全部功能,并且相同质量的无损压缩WebP图片体积比PNG小大约26%;若是是有损压缩,相同质量的WebP图片体积比JPEG小25%-34%。
不少人会认为,既然WebP功能完善、压缩率更高,那直接用WebP取代上述全部的图片压缩格式不就好了吗?其实否则,WebP也有其缺点:咱们知道JPEG是有损压缩而PNG是无损压缩,因此JPEG的压缩率高于PNG;可是有损压缩的算法决定了其压缩时间必定是高于无损压缩的,也就是说JPEG的压缩时间高于PNG。而WebP不管是无损仍是有损压缩,压缩率都分别高于PNG和JPEG;与其相对应的是其压缩时间也比它们长的多。经测试,WebP图片的编码时间比JPEG长8倍。能够看出,时间和空间是一对矛盾;若是想要节省更多的空间,必然要付出额外的时间;若是想要节省时间,那么必然要付出空间的代价。这取决于咱们在实际中对于时空不一样的需求程度来作出选择。
无论怎么说,WebP仍是一种强大的、理想的图片压缩格式,而且借由 Google 在网络世界的影响力,WebP 在几年的时间内已经获得了普遍的应用。看看你手机里的 App:微博、微信、QQ、淘宝等等,每一个 App 里都有 WebP 的身影。
另外,WebP是Android4.0才引入的一种图片压缩格式,若是想要在Android4.0之前的版本支持WebP格式的图片,那么须要借助于第三方库来支持WebP格式图片,例如:webp-android-backport函数库,该开源项目在GitHub地址为: https://github.com/alexey-pelykh/webp-android-backport  固然考虑到通常的Android开发中只须要向下兼容到Android4.0便可,因此也能够忽略这个问题。
 
目前来讲,以上所述的五种压缩格式,Android操做系统都提供了原生支持;可是在上层能直接调用的编码方式只有 JPEG、PNG、WebP 这三种。具体的,能够查看Bitmap类的枚举内部类CompressFormat类的枚举值来获取上层能调用的图片编码方式。你会发现枚举值也是JPEG、PNG和WEBP三种。
若是咱们想要在应用层使用Gif格式图片,须要自行引入第三方函数库来提供对Gif格式图片的支持。不过通常咱们用WebP取代Gif。
所以,咱们只须要比较分析PNG、JPEG、WebP这三种压缩格式便可。
 
比较分析:
①对于摄影类等真彩图片:由于咱们对这类色彩丰富的图片的透明度没有要求(通常默认为不透明),能够采用JPEG有损压缩格式,由于JPEG自己就不支持透明度,并且由于是有损压缩,因此尽管会牺牲一丢丢照片的质量可是能够大大减小体积。若是非要采用PNG格式,那么首先由于PNG支持透明度通道,因此明明没必要要的透明度值却会被存储;其次由于是无损压缩,因此压缩率不会很高从而致使保存的图片很是大!综上比较,建议采用JPEG格式,不要用PNG格式。
JPEG格式能够与Bitmap.Config参数值为RGB_565搭配使用,这是一个理想的设置。
 
②对于logo图标、背景图等图片:这类图片的特色是每每是有大块的颜色相同的区域,这与无损压缩的思路不谋而合(即删除重复数据)。并且这类图片对透明度是有要求的,所以能够采用PNG无损压缩格式;尽管使用PNG格式会让图片有点大,可是能够在后续进行PNG图片优化以对APP体积进行瘦身。若是非要采用JPEG格式,那么因为有损压缩的原理(利用人脑的自动补全机制),可能会随机地丢失一些线条致使最终的图片彻底不是想要的效果。综上比较,建议使用PNG格式,不要用JPEG格式。
PNG格式能够与Bitmap.Config参数值为ARGB_8888搭配使用,这是一个理想的设置。
固然,以上两种状况,咱们均可以使用WebP取代PNG或JPEG,若是咱们想要这么作的话。若是你的项目中对空间的需求程度更高,你彻底有理由这么作。可是若是你对空间需求程度还OK,你也能够选择分状况使用PNG或JPEG格式。
 
六、图片优化
图片优化属于Android性能优化的一种,这里主要是针对PNG图片的大小进行优化,毕竟PNG这种无损压缩格式每每会致使图片都比较大。除非你的项目已经全面支持了WebP格式,不然对PNG格式图片的优化都会是你必须考虑的一点,这有利于减小APP的体积大小。
对PNG图片进行优化的思想是:减小PNG图片的体积,经常使用方式有:
①无损压缩工具ImageOptim
ImageOptim是一种无损压缩工具,因此你不用担忧利用该工具对PNG图片进行压缩后图片质量会受影响。它的压缩原理是:优化PNG压缩参数,移除冗余元数据以及非必需的颜色配置文件等,在不牺牲图片质量的前提下,既减小了PNG图片的大小,又提升了其加载的速度。
ImageOptim工具的网址为: https://imageoptim.com
 
②有损压缩工具ImageAlpha
ImageAlpha与ImageOptim是同一个做者,不过ImageAlpha属于有损压缩,所以图片质量会受到影响。因此使用ImageAlpha对PNG图片进行压缩后,必须让设计师检视一下优化后的PNG图片,以避免影响APP的视觉效果。可是ImageAlpha的优势是能够极大减小PNG图片的体积大小。
ImageAlpha工具的网址为: https://pngmini.com
 
③有损压缩TinyPNG
前面两个工具是应用程序,TinyPNG是一个Web站点。你能够上传原PNG图片,它对PNG图片压缩后你就能够下载优化后的结果了。由于TinyPNG也是有损压缩,因此优缺点同②
TinyPNG的网址为: https://tinypng.com
以上方案都属于对PNG图片进行二次压缩(有的是有损有的是无损),咱们须要在图片质量和图片大小这对矛盾中根据实际状况进行选择。
 
④PNG/JPEG转换为WebP
若是不想对PNG图片进行二次压缩,能够考虑直接将其替换为WebP格式的图片。另外,咱们对JPEG格式的图片也能够这么替换。毕竟WebP不管是与PNG仍是与JPEG格式想比,压缩后体积大小都小不少。WebP转换工具备:
智图,这是一个图片优化平台,地址为: https://zhitu.isux.us
iSparta,这是一个针对PNG图片的二次压缩和格式转换工具,地址为: https://isparta.github.io
 
⑤使用NinePatch格式的PNG图
.9.png图片格式简称为NinaPatch图,本质上仍然是PNG格式图片。不过它的优势是体积小、拉伸不变形,可以很好地适配Android各类机型。咱们能够利用Android Studio提供的功能,右键一张PNG图片点击“create 9=Patch File”便可完成转换。
总结:不管是二次压缩仍是格式转换,不管是有损二次压缩仍是无损二次压缩,咱们都须要根据实际需求进行方案和工具的选择。
 
咱们已经知道了Android中图片内存中的表示形式(Bitmap)和磁盘上的表示形式(各类压缩格式),以及两者的关系(压缩和解压缩的过程)。下面具体看看Bitmap的使用方式和注意事项,毕竟磁盘上存储的图片终究仍是要加载到内存中以Bitmap的形式进行展现的。
 
4、Bitmap的简单使用
先看看Bitmap的简单使用方式:
一、Bitmap的加载方法
Bitmap的工厂类BitmapFactory提供了四类静态方法用于加载Bitmap对象:decodeFile、decodeResource、decodeStream、decodeByteArray。
分别表明从本地图片文件、项目资源文件、流对象(能够是网络输入流对象或本地文件输入流对象)、字节序列中加载一个Bitmap对象。
以前讲的图片的三个来源,均可以找到对应的decodeXXXX方法来获取该图片对应的Bitmap对象。
举个例子,假设须要经过网络请求一张图片资源并展现:先处理该网络请求并获得返回结果的输入流对象;依据该流对象调用decodeStream方法获得一个Bitmap对象,该对象即表示这张图片的内容;最后经过ImageView的setImageBitmap()方法显示该图片便可。
 
二、Bitmap的压缩存储方法
Bitmap的压缩存储与Bitmap的加载是相反的过程,经过compress()方法来实现,该方法原型为:
compress(Bitmap.CompressFormat format, int quality, OutputStream stream)
format参数表示压缩存储的格式,可选为PNG、JPEG和WEBP;quality表示压缩率,取值在0~100之间,100表示未压缩,30表示压缩为原大小的30%,可是该参数在format值为PNG时无效,由于PNG属于无损压缩没法设置压缩率;stream就是但愿输出到某个位置的输出流对象,好比某个文件的输出流对象。
经过compress()方法能够将Bitmap按照指定的格式和压缩率(非PNG格式时)压缩存储到指定的位置。
 
三、BitmapFactory.Options类
BitmapFactory是Bitmap的工厂类,经过BitmapFactory的静态方法来建立Bitmap对象;BitmapFactory.Options类表明对Bitmap对象的属性设置(配置)。通常状况下,咱们调用decodeXXXX方法时不须要传递一个BitmapFactory.Options对象做为参数,所以此时是利用默认的配置信息来建立Bitmap对象。若是须要对建立的Bitmap对象进行自定义的配置,那么就须要给decodeXXXX方法传递一个BitmapFactory.Options对象,该对象包含了对Bitmap对象的配置信息。
经过BitmapFactory.Options类的构造器建立BitmapFactory.Options对象。该对象包含的Bitmap配置信息即为该对象的各属性值,主要有:

介绍一下比较很差理解的属性:

①inJustDecodeBounds:这个属性表示是否只扫描轮廓,默认为false。若是该属性为true,decodeXXXX方法不会返回一个Bitmap对象(即不会为Bitmap分配内存)而是返回null。那若是decodeXXXX方法再也不分配内存以建立一个Bitmap对象,那么还有什么用呢?答案就是:扫描轮廓。

BitmapFactory.Options对象的outWidth和outHeight属性分别表明Bitmap对象的宽和高,可是这两个属性在Bitmap对象未建立以前显然默认为0,默认只有在Bitmap对象建立后才能被赋予正确的值。而当inJustDecodeBounds属性为true,虽然不会分配内存建立Bitmap对象,可是会扫描轮廓来给outWidth和outHeight属性赋值,就至关于绕过了Bitmap对象建立的这一步提早获取到Bitmap对象的宽高值。那这个属性到底有啥用呢?具体用处体如今Bitmap的采样率计算中,后面会详细介绍。

②inSample:这个表示Bitmap的采样率,默认为1。好比说有一张图片是2048像素X1024像素,那么默认状况下该图片加载到内存中的Bitmap对象尺寸也是2048像素X1024像素。若是采用的是ARGB_8888方式,那么该Bitmap对象加载所消耗的内存为2048X1024X4/1024/1024=8M。这只是一张图片消耗的内存,若是当前活动须要加载几张甚至几十张图片,那么会致使严重的OOM错误。

OOM错误:尽管Android设备内存大小可能达到好几个G(好比4G),可是Andorid中每一个应用其运行内存都有一个阈值,超过这个阈值就会引起out of memory即OOM错误(内存溢出错误)。由于如今市场上流行的手机设备其操做系统都是在Andori原生操做系统基础上的拓展,因此不一样的设备环境中这个内存阈值不同。能够经过如下方法获取到当前应用所分配的内存阈值大小,单位为字节: Runtime.getRuntime().maxMemory();

尽管咱们确实能够经过设置来修改这个阈值大小以提升应用的最大分配内存(具体方式是在在Manifest中设置android.largeHeap="true"),可是须要注意的是:内存是一种很宝贵的资源,不加考虑地无脑给每一个应用提升最大分配内存是一个糟糕的选择。由于手机总内存相比较每一个应用默认的最大分配内存虽然高不少,可是手机中的应用数量是很是多的,每一个应用都修改其运行内存阈值为几百MB甚至一个G,这很严重影响手机性能!另外,若是应用的最大分配内存很高,这意味着其垃圾回收工做也会变得更加耗时,这也会影响应用和手机的性能。因此,这个方案须要慎重考虑不能滥用。

关于这个方案的理解能够参考一位大神的解释:“在一些特殊的情景下,你能够经过在manifest的application标签下添加largeHeap=true的属性来为应用声明一个更大的heap空间。而后,你能够经过getLargeMemoryClass()来获取到这个更大的heap size阈值。然而,声明获得更大Heap阈值的本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的由于你须要使用更多的内存而去请求一个大的Heap Size。只有当你清楚的知道哪里会使用大量的内存而且知道为何这些内存必须被保留时才去使用large heap。所以请谨慎使用large heap属性。使用额外的内存空间会影响系统总体的用户体验,而且会使得每次gc的运行时间更长。在任务切换时,系统的性能会大打折扣。另外, large heap并不必定可以获取到更大的heap。在某些有严格限制的机器上,large heap的大小和一般的heap size是同样的。所以即便你申请了large heap,你仍是应该经过执行getMemoryClass()来检查实际获取到的heap大小。”

综上,咱们已经知道了Bitmap的加载是一个很耗内存的操做,特别是在大位图的状况下。这很容易引起OOM错误,而咱们又不能轻易地经过修改或提供应用的内存阈值来避免这个错误。那么咱们该怎么作呢?答案就是:利用这里所说的采样率属性来建立一个原Bitmap的子采样版本。这也是官方推荐的对于大位图加载的OOM问题的解决方案。其具体思想为:好比仍是那张尺寸为2048像素X1024像素图片,在inSample值默认为1的状况下,咱们如今已经知道它加载到内存中默认是一个2048像素X1024像素大位图了。咱们能够将inSample设置为2,那么该图片加载到内存中的位图宽高都会变成原宽高的1/2,即1024像素X512像素。进一步,若是inSample值设置为4,那么位图尺寸会变成512像素X256像素,这个时候该位图所消耗的内存(假设仍是ARGB_8888方式)为512X256X4/1024/1024=0.5M,能够看出从8M到0.5M,这极大的节省了内存资源从而避免了OOM错误。

切记:官方对于inSample值的要求是,必须为2的幂,好比二、四、8...等整数值。

这里会有两个疑问:第一:经过设置inSample属性值来建立一个原大位图的子采样版本的方式来下降内存消耗,听不上确实很不错。可是这不会致使图片严重失真吗?毕竟你丢失了那么多像素点,这意味着你丢失了不少颜色信息。对这个疑问的解释是:尽管在采样的过程确实会丢失不少像素点,可是原位图的尺寸也在减少,其像素密度是不变的。好比说若是inSample值为2,那么子采样版本的像素点数量是原来的1/4,可是子采样版本的显示尺寸(区域面积)也会变成原来的1/4,这样的话像素密码是不变的所以图片不用担忧严重失真问题。第二:inSample值如何选取才是最佳?这其实取决于ImageView的尺寸,具体采样率的计算方式后面会详细介绍。

③inPreferredConfig:该属性指定Bitmap的色深值,该属性类型为Bitmap.Config值。

例如你能够指定某图片加载为Bitmap对象的色深模式为ARGB_8888,即:options.inPreferredConfig=Bitmap.Config.ARGB_8888;

④isMutable:该属性表示经过decodeXXXX方法建立的Bitmap对象其表明的图片内容是否容许被外部修改,好比利用Canvas从新绘制其内容等。默认为false,即不容许被外部操做修改。

利用这些属性定制BitmapFactory.Options对象,从而灵活地按照本身的需求配置建立的Bitmap对象。

 

5、Bitmap的进阶使用

一、高效地加载大位图

上面刚说了大位图加载时的OOM问题,解决方式是经过inSample属性建立一个原位图的子采样版本以减低内存。那么这里的采样率inSample值如何选取最好呢?这里咱们利用官方推荐的采样率最佳计算方式:基本步骤就是:①获取位图原尺寸 ②获取ImageView即最终图片显示的尺寸  ③依据两种尺寸计算采样率(或缩放比例)。

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // 位图的原宽高经过options对象获取
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
     //当要显示的目标大小和图像的实际大小比较接近时,会产生不必的采样,先除以2再判断以防止过分采样
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

 

依据上面的最佳采样率计算方法,进一步能够封装出利用最佳采样率建立子采样版本再建立位图对象的方法,这里以从项目图片资源文件加载Bitmap对象为例:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
   //由于inJustDecodeBounds为true,因此不会建立Bitmap对象只会扫描轮廓从而给options对象的宽高属性赋值
    BitmapFactory.decodeResource(res, resId, options);

    // 计算最佳采样率
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 记得将inJustDecodeBounds属性设置回false值
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

 

二、Bitmap加载时的异步问题

因为图片的来源有三种,若是是项目图片资源文件的加载,通常采起了子采样版本加载方案后不会致使ANR问题,毕竟每张图加载消耗的内存不会很大了。可是对于本地图片文件和网络图片资源,因为分别涉及到文件读取和网络请求,因此属于耗时操做。为了不ANR的产生,必须将图片加载为Bitmap对象的过程放入工做线程中;获取到Bitmap对象后再回到UI线程设置ImageView的显示。举个例子,若是采用AsyncTask做为咱们的异步处理方案,那么代码以下:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
         private final ImageView iv;
         private int id = 0;
 
         public BitmapWorkerTask(ImageView imageView) {
             iv = imageView;
          }
 
         // Decode image in background.
         @Override
         protected Bitmap doInBackground(Integer... params) {
             id = params[0];
            //假设ImageView尺寸为500X500,为了方便仍是以项目资源文件的加载方式为例,由于这能够复用上面封装的方法
             return decodeSampledBitmapFromResource(getResources(), id, 500, 500);
        }
 
         @Override
         protected void onPostExecute(Bitmap bitmap) {
             iv.setImageBitmap(bitmap);
         }
     }

该方案中,doInBackground方法执行在子线程,用来处理 ”图片文件读取操做+Bitmap对象的高效加载操做” 或 ”网络请求图片资源操做+Bimap对象的高效加载操做”等两种情形下的耗时操做。onPostExecute方法执行在UI线程,用于设置ImageView的显示内容。看上去这个方案很完美,可是有一个很隐晦的严重问题:

由当前活动启动了BitmapWorkerTask任务后:当咱们退出当前活动时,因为异步任务只依赖于UI线程因此BitmapWorkerTask任务会继续执行。正常的操做是遍历当前活动实例的对象图来释放各对象的内存以销毁该活动,可是因为当前活动实例的ImageView引用被BitmapWorkerTask对象持有,并且仍是强引用关系。这会致使Activity实例没法被销毁,引起内存泄露问题。内存泄露问题会进一步致使内存溢出错误。

为了解决这个问题,咱们只须要让BitmapWorkerTask类持有ImageView的弱引用便可。这样当活动退出时,BitmapWorkerTask对象因为持有的是ImageView的弱引用,因此ImageView对象会被回收,继而Activity实例获得销毁,从而避免了内存泄露问题。具体修改后的代码以下:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;

        public BitmapWorkerTask(ImageView imageView) {
            // 用弱引用来关联这个imageview!弱引用是避免android 在各类callback回调里发生内存泄露的最佳方法!
            //而软引用则是作缓存的最佳方法 二者不要搞混了!
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //当后台线程结束后 先看看ImageView对象是否被回收:若是被回收就什么也不作,等着系统回收他的资源
            //若是ImageView对象没被回收的话,设置其显示内容便可
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }

 

拓展:①WeakReference是弱引用,其中保存的对象实例能够被GC回收掉。这个类一般用于在某处保存对象引用,而又不干扰该对象被GC回收,能够用于避免内存泄露。②SoftReference是软引用,它保存的对象实例,不会被GC轻易回收,除非JVM即将OutOfMemory,不然不会被GC回收。这个特性使得它很是适合用于设计Cache缓存。缓存能够省去重复加载的操做,并且缓存属于内存所以读取数据很是快,因此咱们天然不但愿缓存内容被GC轻易地回收掉;可是由于缓存本质上就是一种内存资源,因此在内存紧张时咱们须要能释放一部分缓存空间来避免OOM错误。综上,软引用很是适合用于设计缓存Cache。可是,这只是早些时候的缓存设计思想,好比在Android2.3版本以前。在Android2.3版本以后,JVM的垃圾收集器开始更积极地回收软引用对象,这使得本来的缓存设计思想失效了。由于若是使用软引用来实现缓存,那么动不动缓存对象就被GC回收掉实在是没法接受。因此,Android2.3以后对于缓存的设计使用的是强引用关系(也就是普通对象引用关系)。不少人会问这样不会因为强引用的缓存对象没法被回收从而致使OOM错误吗?确实会这样,可是咱们只须要给缓存设置一个合理的阈值就行了。将缓存大小控制在这个阈值范围内,就不会引起OOM错误了。

 

三、列表加载Bitmap时的图片显示错乱问题

 咱们已经知道了如何高效地加载位图以免OOM错误,还知道了如何合理地利用异步机制来避免Bitmap加载时的ANR问题和内存泄露问题。如今考虑另外一种常见的Bitmap加载问题:当咱们使用列表,如ListView、GridView和RecyclerView等来加载多个Bitmap时,可能会产生图片显示错乱的问题。先看一下该问题产生的缘由。以ListView为例:

①ListView为了提升列表展现内容在滚动时的流畅性,使用了一种item复用机制,即:在屏幕中显示的每一个ListView的item对应的布局只有在第一次的时候被加载,而后缓存在convertView里面,以后滑动改变ListView时调用的getView就会复用缓存在converView中的布局和控件,因此可使得ListView变得流畅(由于不用重复加载布局)。

②每一个Item中的ImageView加载图片时每每都是异步操做,好比在子线程中进行图片资源的网络请求再加载为一个Bitmap对象最后回到UI线程设置该item的ImageView的显示内容。

③ 听上去①是一种很是合理有效的提升列表展现流畅性的机制,②看起来也是图片加载时很常见的一个异步操做啊。其实①和②自己都没有问题,可是①+②+用户滑动列表=图片显示错乱!具体而言:当咱们在其中一个itemA加载图片A的时候,因为加载过程是异步操做须要耗费必定的时间,那么有可能图片A未被加载完该itemA就“滚出去了”,这个itemA可能被当作缓存应用到另外一个列表项itemB中,这个时候恰好图片A加载完成显示在itemB中(由于ImageView对象在缓存中被复用了),本来itemB该显示图片B,如今显示图片A。这只是最简单的一种状况,当滑动频繁时这种图片显示错乱问题会越发严重,甚至让人毫无头绪。

那么如何解决这种图片显示错乱问题呢?解决思路其实很是简单:在图片A被加载到ImageView以前作一个判断,判断该ImageView对象是否仍是对应的是itemA,若是是则将图片加载到ImageView当中;若是不是则放弃加载(由于itemB已经启动了图片B的加载,因此不用担忧控件出现空白的状况)。

那么新的问题出现了,如何判断ImageView对象对应的item已经改变了?咱们能够采起下面的方式:

①在每次getView的复用布局控件时,对会被复用的控件设置一个标签(在这里就是对ImageView设置标签)。标签内容必须能够标识不一样的item!这里使用图片的url做为标签内容,而后再异步加载图片。

②在图片下载完成后要加载到ImageView以前作判断,判断该ImageView的标签内容是否和图片的url同样:若是同样说明ImageView没有被复用,能够将图片加载到ImageView当中;若是不同,说明ListView发生了滑动,致使其余item调用了getView从而将该ImageView的标签改变,此时放弃图片的加载(尽管图片已经被下载成功了)。

总结:解决ListView异步加载Bitmap时的图片错乱问题的方式是:为被复用的控件对象(即ImageView对象)设置标签来标识item,异步任务结束后要将图片加载到ImageView时取出标签值进行比对是否一致:若是一致意味着没有发生滑动,正常加载图片;若是不同意味着发生了滑动,取消加载。

 

四、Android中的Bitmap缓存策略

若是只是加载若干张图片,上述的Bitmap使用方式已经绝对够用了;可是若是在应用中须要频繁地加载大量的图片,特别是有些图片会被重复加载时,这个时候利用缓存策略能够很好地提升图片的加载速度。好比说有几张图片被重复加载的频率很高,那么能够在缓存中保留这几张图片的Bitmap对象;后续若是须要加载这些图片,则不须要花费不少时间去从新在网络上获取并加载这些图片的Bitmap对象,只须要直接向缓存中获取以前保留下来的Bitmap对象便可。

Android中对Bitmap的缓存策略分为两种:

  • 内存缓存:图像存储在设备内存中,所以访问速度很是快。事实上,比图像解码过程要快得多,因此将图像存储在这里是让app更快更稳定的一个好主意。内存缓存的惟一缺点是:它只存活于app的生命周期,这意味着一旦app被Android操做系统内存管理器关闭或杀死(所有或部分),那么储存在那里的全部图像都将丢失。因为内存缓存本质上就是一种内存资源,因此切记:内存缓存必须设置一个最大可用的内存量。不然可能会致使臭名昭著的outOfMemoryError。
  • 磁盘缓存:图像存储在设备的物理存储器上(磁盘)。磁盘缓存本质上就是设备SD卡上的某个目录。只要app不被卸载,其磁盘缓存能够一直安全地存储图片,只要有足够的磁盘空间便可。缺点是,磁盘读取和写入操做可能会很慢,并且老是比访问内存缓存慢。因为这个缘由,所以全部的磁盘操做必须在工做线程执行,UI线程以外。不然,app会冻结,并致使ANR警报。

在实际使用中,咱们不须要强行二选一,能够两者都使用,毕竟各有优点。因此Android中完整的图片缓存策略为:先尝试在内存缓存中查找Bitmap对象,若是有直接加载使用;若是没有,再尝试在磁盘缓存中查找图片文件是否存在,若是有将其加载至内存使用;若是仍是没有,则老老实实发送网络请求获取图片资源并加载使用。须要注意的是,后面两种状况下的操做都必须使用异步机制以免ANR的发生。

Android中经过LruCache实现内存缓存,经过DiskLruCache实现磁盘缓存,它们采用的都是LRU(Least Recently Used)最近最少使用算法来移除缓存中的最近不常访问的内容(变相地保留了最近常常访问的内容)。

①内存缓存LruCache

LruCache原理:LruCache底层是使用LinkedHashMap来实现的,因此LruCache也是一个泛型类。在图片缓存中,其键类型是字符串,值类型为Bitmap。利用LinkedHashMap的accessOrder属性能够实现LRU算法。accessOrder属性决定了LinkedHashMap的链表顺序:accessOrder为true则以访问顺序维护链表,即被访问过的元素会安排到链表的尾部;accessorder为false则以插入的顺序维护链表。

而LruCache利用的正是accessOrder为true的LinkedHashMap来实现LRU算法的。具体表现为:

1° put:经过LinkedHashMap的put方法来实现元素的插入,插入的过程仍是要先寻找有没有相同的key的数据,若是有则替换掉旧值,而且将该节点移到链表的尾部。这能够保证最近常常访问的内容集中保存在链表尾部,最近不常访问的内存集中保存在链表头部位置。在插入后若是缓存大小超过了设定的最大缓存大小(阈值),则将LinkedHashMap头部的节点(最近不常访问的内容)删除,直到size小于maxSize。

2° get:经过LinkedHashMap的get方法来实现元素的访问,因为accessOrder为true,所以被访问到的元素会被调整到链表的尾部,所以不常被访问的元素就会留到链表的头部,当触发清理缓存时不常被访问的元素就会被删除,这里是实现LRU最关键的地方。

3° remove:经过LinkedHashMap的remove方法来实现元素的移除。

3° size:LruCache中很重要的两个成员变量size和maxSize,由于清理缓存的是在size>maxSize时触发的,所以在初始化的时候要传入maxSize定义缓存的大小,而后重写sizeOf方法,由于LruCache是经过sizeOf方法来计算每一个元素的大小。这里咱们是使用LruCache来缓存图片,因此sizeOf方法须要计算Bitmap的大小并返回。

LruCache对其缓存对象采用的是强引用关系,采用maxSize来控制缓存空间大小以免OOM错误。并且LruCache类在Android SDK中已经提供了,在实际使用中咱们只须要完成如下几步便可:

  • 设计LruCache的最大缓存大小:通常是经过计算当前可用的内存大小继而来获取到应该设置的缓存大小
  • 建立LruCache对象:传入最大缓存大小的参数,同时重写sizeOf方法来设置存在LruCache里的每一个对象的大小
  • 封装对LruCache的数据访问和添加操做并对外提供接口以供调用

具体代码参考以下:

//初始化LruCache对象
public void initLruCache()
{
    //获取当前进程的可用内存,转换成KB单位
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    //分配缓存的大小
    int maxSize = maxMemory / 8;
    //建立LruCache对象并重写sizeOf方法
    lruCache = new LruCache<String, Bitmap>(maxSize)
        {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO Auto-generated method stub
                return value.getWidth() * value.getHeight() / 1024;
            }
        };
}

/**
 * 封装将图片存入缓存的方法
 * @param key 图片的url转化成的key
 * @param bitmap对象
 */
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
{
    if(getBitmapFromMemoryCache(key) == null)
    {
        mLruCache.put(key, bitmap);
    }
}

//封装从LruCache中访问数据的方法
private Bitmap getBitmapFromMemoryCache(String key)
{
    return mLruCache.get(key);
}

/**
 * 由于外界通常获取到的是url而不是key,所以为了方便再作一层封装
 * @param url http url
 * @return bitmap
 */
private Bitmap loadBitmapFromMemoryCache(String url)
{
    final String key = hashKeyFromUrl(url);
    return getBitmapFromMemoryCache(key);
}

 

②磁盘缓存DiskLruCache

因为DiskLruCache并不属于Android SDK的一部分,须要自行设计。与LruCache实现LRU算法的思路基本上是一致的,可是有不少不同的地方:LruCache是内存缓存,其键对应的值类型直接为Bitmap;而DiskLruCache是磁盘缓存,因此其键对应的值类型应该是一个表明图片文件的类。其次,前者访问或添加元素时,查找成功能够直接使用该Bitmap对象;后者访问或添加元素时,查找到指定图片文件后还须要经过文件的读取和Bitmap的加载过程才能使用。另外,前者是在内存中的数据读写操做因此不须要异步;后者涉及到文件操做必须开启子线程实现异步处理。

具体DiskLruCache的设计方案和使用方式能够参考这篇博客:https://www.jianshu.com/p/765640fe474a

有了LruCache类和DiskLruCache类,能够实现完整的Android图片二级缓存策略:在具体的图片加载时:先尝试在LruCache中查找Bitmap对象,若是有直接拿来使用。若是没有再尝试在DiskLruCache中查找图片文件,若是有将其加载为Bitmap对象再使用,并将其添加至LruCache中;若是没有查找到指定的图片文件,则发送网络请求获取图片资源并加载为Bitmap对象再使用,并将其添加DiskLruCache中。

 

五、Bitmap内存管理

Android设备的内存包括本机Native内存和Dalvik(相似于JVM虚拟机)堆内存两部分。在Android 2.3.3(API级别10)及更低版本中,位图的支持像素数据存储在Native内存中。它与位图自己是分开的,Bitmap对象自己存储在Dalvik堆中。Native内存中的像素数据不会以可预测的方式释放,可能致使应用程序短暂超出其内存限制并崩溃。从Android 3.0(API级别11)到Android 7.1(API级别25),像素数据与相关Bitmap对象一块儿存储在Dalvik堆上,一块儿交由Dalvik虚拟机的垃圾收集器来进行回收,所以比较安全。

①在Android2.3.3版本以前:

在Bitmap对象再也不使用并但愿将其销毁时,Bitmap对象自身因为保存在Dalvik堆中,因此其自身会由GC自动回收;可是因为Bitmap的像素数据保存在native内存中,因此必须由开发者手动调用Bitmap的recycle()方法来回收这些像素数据占用的内存空间。

 

②在Android2.3.3版本以后:

因为Bitmap对象和其像素数据一块儿保存在Dalvik堆上,因此在其须要回收时只要将Bitmap引用置为null 就好了,不须要如此麻烦的手动释放内存操做。

固然,通常咱们在实际开发中每每向下兼容到Android4.0版本,因此你懂得。

 

③在Android3.0之后的版本,还提供了一个很好用的参数,叫options.inBitmap。若是你使用了这个属性,那么在调用decodeXXXX方法时会直接复用 inBitmap 所引用的那块内存。你们都知道,不少时候ui卡顿是由于gc 操做过多而形成的。使用这个属性能避免频繁的内存的申请和释放。带来的好处就是gc操做的数量减小,这样cpu会有更多的时间执行ui线程,界面会流畅不少,同时还能节省大量内存。简单地说,就是内存空间被各个Bitmap对象复用以免频繁的内存申请和释放操做。

须要注意的是,若是要使用这个属性,必须将BitmapFactory.Options的isMutable属性值设置为true,不然没法使用这个属性。

具体使用方式参考以下代码:

final BitmapFactory.Options options = new BitmapFactory.Options();
        //size必须为1 不然是使用inBitmap属性会报异常
        options.inSampleSize = 1;
        //这个属性必定要在用在src Bitmap decode的时候 否则你再使用哪一个inBitmap属性去decode时候会在c++层面报异常
        //BitmapFactory: Unable to reuse an immutable bitmap as an image decoder target.
        options.inMutable = true;
        inBitmap2 = BitmapFactory.decodeFile(path1,options);
        iv.setImageBitmap(inBitmap2);
        //将inBitmap属性表明的引用指向inBitmap2对象所在的内存空间,便可复用这块内存区域
        options.inBitmap = inBitmap2;
        //因为启用了inBitmap属性,因此后续的Bitmap加载不会申请新的内存空间而是直接复用inBitmap属性值指向的内存空间
        iv2.setImageBitmap(BitmapFactory.decodeFile(path2,options));
        iv3.setImageBitmap(BitmapFactory.decodeFile(path3,options));
        iv4.setImageBitmap(BitmapFactory.decodeFile(path4,options));

 

补充:Android4.4之前,你要使用这个属性,那么要求复用内存空间的Bitmap对象大小必须同样;可是Android4.4 之后只要求后续复用内存空间的Bitmap对象大小比inBitmap指向的内存空间要小就可使用这个属性了。另外,若是你不一样的imageview 使用的scaletype 不一样,可是你这些不一样的imageview的bitmap在加载是若是都是引用的同一个inBitmap的话,

这些图片会相互影响。综上,使用inBitmap这个属性的时候 必定要当心当心再当心。

 

6、开源框架

咱们如今已经知道了,Android图片加载的知识点和注意事项实在太多了:单个的位图加载咱们要考虑Bitmap加载的OOM问题、异步处理问题和内存泄露问题;列表加载位图要考虑显示错乱问题;频繁大量的位图加载时咱们要考虑二级缓存策略;咱们还有考虑不一样版本下的Bitmap内存管理问题,在这部分最后咱们介绍了Bitmap内存复用方式,咱们须要当心使用这种方式。

那么,能不能有一种方式让咱们省去这么多繁琐的细节,方便咱们对图片进行加载呢?答案就是:利用已有的成熟的图片加载和缓存开源框架!好比square公司的Picasso框架、Google公司的Glide框架和Facebook公司的Fresco框架等。特别是Fresco框架,提供了三级缓存策略,很是的专业。根据APP对图片显示和缓存的需求从低到高排序,咱们能够采用的方案依次为:Bitmapfun、Picasso、Android-Universal-Image-Loader、Glide、Fresco。

这些框架能够方便咱们实现对网络图片的加载和缓存操做。具体再也不赘述。

相关文章
相关标签/搜索