图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco、Picasso、UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降低了OOM发生的概率。然而,在图片加载方面我们是否可以就此放松警惕了呢?

开源图片加载库能为我们解决绝大部分有关图片的问题,然而并不是所有!

首先,图片从来源上可以分成三大类:网络图片、手机图片、APK资源图片。网络图片和手机图片都在图片加载库功能的覆盖范围内,基本上不用开发者太操心,但是APK资源图片却不在此范围!

关于APK资源图片有3个特征: 
1、资源图片基本都是在xml中引用 ,在Java中也是通过资源ID查找 。 
2、资源图片一般不使用异步记载,不会出现loading图这些中间状态。 
3、资源图片不会加载失败,如果失败了那么APP也挂掉了。

正是由于这3点特征,所以图片加载库实在鞭长莫及。那么就很容易出现一个问题:图片过大导致OOM!

很多APP为了追求酷炫的效果,热衷于设计绚丽全屏背景页面。既然是为了炫酷,考虑到用户体验,这些全屏背景图自然不能使用网络图片了,所以,这些图片都被放在apk包中作为资源文件直接引用。

使用这些资源图片的方式一般都是:

<code class="hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">android:background=<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">@drawable</span>/xxx"</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

正常情况下,这样使用自然不会出现问题,但是如果APP内存紧张,很容易就出现OOM,尤其是5.0版本以下的手机,经常跑着跑着就Crash了,始作俑者就是这个。

为了解决这种问题,最常用的方式是找设计师压缩图片。而压缩图片有两种方式:缩小尺寸和降低质量。那么,这两种方式是否有效呢?

1、缩小尺寸: 压缩图片的宽度和高度。由于图片的内存占用与宽高成正比,这种方式确实有效,但是图片显示时会被拉伸导致变形,从而失却美感。

2、降低质量: 降低图片的色彩度,像素颜色密度。这其实是一个误区,很多人认为图片的存储占用空间小,图片的内存占用就会小,其实是错误的观点。这是方式并不会影响图片的内存占用,反而由于质量降低(下文具体分析),使得页面缺乏质感。必须记住:图片的内存占用与图片质量毫无干系!

为了寻求一个合理的解决方案,必须知彼知己。下面,我们来详细分析下资源图片的内存占用的情况!(后文所说的图片,除非特殊指明,否则都默认指APK资源图片)。


1、计算Bitmap的内存占用

我们以一张标准720p的全屏图片为例,宽高比为720x1280,对应的资源文件夹为drawable-xhdpi。同样,设备以标准720p的小米2S手机为例,density=320。

首先,android设备上图片都被处理成Bitmap对象。生成Bitmap有一个非常重要的参数Config,属性值有ALPHA_8、RGB_565、ARGB_4444、ARGB_8888四种。不同的属性值对应的图片每个像素点占用内存大小不同,ALPHA_8每个像素占用1byte,RGB_565和ARGB_4444占用2byte,ARGB_8888占用4byte,其中ARGB_4444在高版本中已经废弃。

那么,资源图片被decode成Bitmap的时候,Config参数值是哪个呢?来看几段代码。

Resources.java

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>final String file = value.string.toString();<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>final Drawable dr;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (file.endsWith(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">".xml"</span>)) {final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"drawable"</span>);dr = Drawable.createFromXml(this, rp, theme);rp.close();} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);dr = Drawable.createFromResourceStream(this, value, is, file, null);is.close();}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul>

Drawable.java

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">public static Drawable createFromResourceStream(Resources res, TypedValue value,InputStream is, String srcName, BitmapFactory.Options opts) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (opts == null) opts = new BitmapFactory.Options();opts.inScreenDensity = res != null ? res.getDisplayMetrics().noncompatDensityDpi : DisplayMetrics.DENSITY_DEVICE;Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> null;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

BitmapFactory.java

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">public static class Options {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>/*** Image are loaded with the {@link Bitmap.Config<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">#ARGB_8888} config by default.</span>*/public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

从上面资源文件生成BitmapDrawable的代码可知,Bitmap.Config使用的是默认值ARGB_8888,即图像每个像素点占用内存4byte。

我们图片的尺寸是720x1280,也就是说像素点个数是720x1280=921600,所有像素点占用内存=720x1280x4=3686400 byte=3.515625M,这个大小是图片不做任何处理时占用的内存大小。

另外,不管图片的内容是什么样子,体现在内存中的也仅仅是每个像素点对应的字节的值不同,大小是一样的,即一张720x1280的空白图和一张720x1280的彩色绚图占用内存大小是一致的。所以说想要降低占用内存,唯有减小宽高尺寸。

刚刚说过,计算出来的3.515625M大小是图片未作任何处理时的大小,但是系统在将图片处理成Drawable对象的时候是否未作处理呢?答案是:不!

BitmapFactory.java

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> Bitmap <span class="hljs-title" style="box-sizing: border-box;">decodeResourceStream</span>(Resources res, TypedValue <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>,InputStream <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, Rect pad, Options opts) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (opts == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {opts = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Options();}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (opts.inDensity == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> && <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span> != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {final <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> density = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>.density;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (density == TypedValue.DENSITY_DEFAULT) {opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (density != TypedValue.DENSITY_NONE) {opts.inDensity = density;}}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (opts.inTargetDensity == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> && res != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {opts.inTargetDensity = res.getDisplayMetrics().densityDpi;}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> decodeStream(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, pad, opts);
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>

代码中Options有两个非常重要的参数,inDensity和inTargetDensity,先来解释一下这俩参数的作用。

inDensity表示被设定的图像密度,决定这个值的是图片所放置的文件目录,比如drawable-hdpi、drawable-xhdpi等等,其对应的density如下表: 
 
代码中opts.inDensity 被赋值为 value.density,也就是资源维度对应的密度值。如果图片放在drawable-hdpi下,inDensity=240,如果放在drawable-xhdpi下,inDensity=320。

inTargetDensity表示最终需要适配到的图片密度,这个值由手机设备来决定,上面代码中其值为DisplayMetrics的densityDpi,手机屏幕越高清这个值越大,而我们例子中720p的小米2S对应的densityDpi=320。

如果inDensity的值和inTargetDensity的值不相等,那么图片尺寸就被会缩放,缩放的比例为 inTargetDensity / inDensity。当然,宽高是需要同时等比缩放的,不然图片就变形了。

前面说过图片占用内存与图片的尺寸有关,如果被尺寸缩放了,内存大小就变了。前面未作任何缩放处理的720x1280图占用内存是3.515625M,假设放在drawable-ldpi目录下inDensity=120,设备inTargetDensity=320,那么最终的占用内存大小将是3.515625Mx(320/120)x(320/120)=25M。

一张图片占用25M大小,很恐怖的一个值,这种情况下,app估计直接挂了,如果放在drawable-hdpi下,占用就是6.25M,drawable-xhdpi下占用是3.515625M。由此可见,图片放置的目录一定要慎重。

最终我们得出一个公式: 
资源图片内存大小 = 宽 x 高 x 4 x (设备密度 / 资源维度密度)x(设备密度 / 资源维度密度)


2、图像后门inPurgeable

前面说到,资源图片防止的目录不对会导致内存占用翻倍,但也不是放的密度维度越高越好,毕竟还是要做适配,不然小尺寸图片显示在高清大屏幕上就不好看了。而即使图片放对位置,占用内存大小也是相当惊人的,来个十张大图应用内存就蹭蹭上去了,冷不丁还来个OOM。

相信很多人都找到过解决方案:inPurgeable,代码网上一搜一大推:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> Bitmap <span class="hljs-title" style="box-sizing: border-box;">readBitmap</span>(Context context, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> resId) {BitmapFactory.Options opt = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> BitmapFactory.Options();opt.inPurgeable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;opt.inInputShareable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;InputStream <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> =context.getResources().openRawResource(resId);<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> BitmapFactory.decodeStream(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>, opt);
}
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

那么,这段代码是否起效果呢?答案是肯定的,以前经常报OOM的现在都好了,而且用AS的内存监视器一看,加载图片时基本上不占内存。不管有没有其它问题,姑且把这个称之为图像后门吧。

下面,我们来看这个后门为什么能起效果!

BitmapFactory.java

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (is == null) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> null;}Bitmap bm = null;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (is instanceof AssetManager.AssetInputStream) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {bm = decodeStreamInternal(is, outPadding, opts);}
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> Bitmap <span class="hljs-title" style="box-sizing: border-box;">decodeStreamInternal</span>(InputStream <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, Rect outPadding, Options opts) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">byte</span> [] tempStorage = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (opts != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) tempStorage = opts.inTempStorage;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (tempStorage == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) tempStorage = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">byte</span>[DECODE_BUFFER_SIZE];<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> nativeDecodeStream(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, tempStorage, outPadding, opts);
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> native Bitmap <span class="hljs-title" style="box-sizing: border-box;">nativeDecodeStream</span>(InputStream <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">byte</span>[] storage,Rect padding, Options opts);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

很明显,decodeStream这段代码最终调用的是native层的类库,我们追踪下去查看(下面以JellyBean源码为例)。

BitmapFactory.cpp

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,jobject options, bool allowPurgeable, bool forcePurgeable = false,bool applyScale = false, float scale = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.</span>0f) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!isPurgeable) {decoder->setAllocator(&javaAllocator);}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isPurgeable) {decodeMode = SkImageDecoder::kDecodeBounds_Mode;}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isPurgeable) {pr = installPixelRef(bitmap, stream, sampleSize, doDither);} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {pr = bitmap->pixelRef();}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span> }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul>
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,int sampleSize, bool ditherImage) {SkImageRef* pr;// only use ashmem <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> large images, since mmaps come at a price<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (bitmap->getSize() >= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span> * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1024</span>) {pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> pr;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

相关isPurgeable的代码就这么多,最终关于图片的decode逻辑都在installPixelRef中,有一段逻辑值得玩味。如果图片大小(占用内存)大于32x1024=32K,那么就使用Ashmem,否则就就放入一个引用池中。

这个做法也很容易理解,如果图片不大,直接放到native层内存中,读取方便且迅速。如果图片过大,放到native层内存也就不合理了,不然图片一多,native层内存很难管理。但是如果使用Ashmem匿名共享内存方式,写入到设备文件中,需要时再读取就能避免很大的内存消耗了,另外,这块内存是由Linux系统的内存管理来管理的,系统内存不足可以直接回收。而且,由于Ashmem跨进程的特性,同一张图片内存是可以跨进程共享的,这也是inInputShareable属性的由来。

关于Ashmem不明白的,可以参考老罗的博客:http://blog.csdn.net/luoshengyang/article/details/6664554

由此可见,如果inPurgeable=true,图片所占用的内存就完全与Java Heap无关了,自然就不会有OOM这种烦恼了。

但是,万事有利有弊,一件事情的成功往往是用牺牲其它方面换来的。

前面说过,使用Resources获取图片Drawable的时候,会默认使用inDensity和inTargetDensity属性缩放图片来达到适配不同分辨率屏幕的目的。但是如果设置了inPurgeable=true来避免在Java Heap中分配内存,inDensity和inTargetDensity这两个属性就不能再使用了,否则即使inPurgeable=true,图片仍然会在Java Heap中分配内存。关于这一点,从以下代码中可以验证:

BitmapFactory.cpp

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,jobject options, bool allowPurgeable, bool forcePurgeable = false,bool applyScale = false, float scale = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.</span>0f) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>bool willScale = applyScale && scale != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.</span>0f;bool isPurgeable = !willScale &&(forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>

在doDecode方法中,isPurgeable会重新赋值,首决条件是图片不会缩放(willScale),其次才会判断Options中的isPurgeable属性。很明显,如果inDensity和inTargetDensity两个属性断定图片需要缩放,isPurgeable会被强制设定成false。这么做的原因很简单,Ashmem不可能维护多套不同尺寸的相同图片。

如果要解决这种适配问题,唯一的解决方案就是图片切成不同的尺寸,放到不同维度的drawable目录下。这样虽然不能做到精准适配,但是可以做到大体适配。原理就是,不同分辨率的屏幕decode相匹配密度维度目录下的对应尺寸图片。

说完适配的问题,你以为坑就到此结束了?其实不然,真正的大问题不是这个!

我们来看inPurgeable属性的一段官方注释:

While inPurgeable can help avoid big Dalvik heap allocations (from API level 11 onward), it sacrifices performance predictability since any image that the view system tries to draw may incur a decode delay which can lead to dropped frames。

意思就是:虽然inPurgeable能避免在Heap中分配一大段内存,但这个是以牺牲性能为代价的,如果图片要绘制到View上可能出现延时导致掉帧。

前面说过,inPurgeable=true的情况下,大图使用Ashmem共享内存存储图片,但是这部分内存存储的仅仅是解码前的图片数据,我们获取的Bitmap只是一个空包弹,不含任何像素信息。当图片需要渲染的时候,先要对一个个像素点进行解码,这个过程是比较耗时的,而偏偏又发生在UI线程中,必须等图像解码完成,UI线程才能继续渲染。如果图片像素点过多,计算量大,很容易就导致卡帧。最坑爹的是,Ashmem内存是由Linux系统统一管理的,如果系统内存紧张,这块儿图片内存很容易被回收,当图片再次被渲染时,Ashmem设备文件就需要重新映射内存再重新解码。

综上所诉,虽然inPurgeable既能导致适配问题,又可能导致性能问题,那么我们为什么还要使用呢?理由很简单:相对于出现OOM导致Crash,这两点牺牲仍然是值得的!

Facebook出品的大名鼎鼎的图片加载库Fresco中对图片的处理都使用了inPurgeable=true,代码如下 :

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BitmapFactory<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Options</span> = new BitmapFactory<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Options</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
options<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.inPurgeable</span> = true<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
Bitmap bitmap = BitmapFactory<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.decodeByteArray</span>(jpeg, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, jpeg<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.length</span>, options)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

虽然,Fresco和我们所说的资源图片干系并不大,但是很多思想还是值得我们借鉴。另外,关于inPurgeable的问题点以及Fresco为什么仍然继续使用这个属性,在其介绍文章中写得很详细,有兴趣可以去阅读下: https://code.facebook.com/posts/366199913563917 (需要翻墙)。


3、被堵上的后门你还在用?

很多时候,知其然而不知其所以然,很容易出问题,如果又不关注版本变化,就肯定会出问题,inPurgeable就是一个很典型的例子。

在5.0版本及以上,inPurgeable这个属性已经被标志为过时了!即使inPurgeable=true,也不会再使用Ashmem内存存放图片,而是直接放到了Java Heap中,简而言之就是inPurgeable属性被忽略了。

因为Android系统从5.0开始对Java Heap内存管理做了大幅的优化。和以往不同的是,对象不再统一管理和回收,而是在Java Heap中单独开辟了一块区域用来存放大型对象,比如Bitmap这种,同时这块内存区域的垃圾回收机制也是和其它区域完全分开的,这样就使得OOM的概率大幅降低,而且读取效率更高。所以,用Ashmem来存储图片就完全没有必要了,何况后者还会导致性能问题。

既然这样,我们就需要考虑继续使用inPurgeable方式处理资源图片是否合理了。

如果仔细阅读过Resources的源码,会发现对于Drawable对象有一套缓存机制,比如当一张图片被解码成BitmapDrawable对象后,会被存储到缓存中,下次再使用这张图片将优先从缓存中获取,既避免了图片重复decode的耗时,又做到了内存的复用,大体代码如下:

Resources.java

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>// First, check whether we have a cached version of this drawable// that was inflated against the specified theme.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!mPreloading) {final Drawable cachedDrawable = caches.getInstance(key, theme);<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (cachedDrawable != null) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> cachedDrawable;}}// Next, check preloaded drawables. These may contain unresolved theme// attributes.final ConstantState cs;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (isColorDrawable) {cs = sPreloadedColorDrawables.get(key);} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>// If we were able to obtain a drawable, store it <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> the appropriate// cache: preload, not themed, null theme, or theme-specific.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (dr != null) {dr.setChangingConfigurations(value.changingConfigurations);cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li></ul>

在inPurgeable后门被堵上之后,如果我们仍然通过BitmapFactory.decodeStream的方式获取资源图片的Bitmap,就会导致相同的图片重复decode,且多次在Java Heap中开辟内存。

同样的方式,原本在5.0以下可以节省Java Heap内存占用,在5.0及以上反而成了真正内存杀手!

所以在真正使用inPurgeable时是需要区分版本的,最简单的解决方案如下:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> Drawable <span class="hljs-title" style="box-sizing: border-box;">decodeLargeResourceImage</span>(Resources resources, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> resId) {Drawable drawable;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {drawable = resources.getDrawable(resId, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>);} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> {BitmapFactory.Options opt = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> BitmapFactory.Options();opt.inPurgeable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;opt.inInputShareable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;InputStream <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> = resources.openRawResource(resId);drawable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> BitmapDrawable(resources, BitmapFactory.decodeStream(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>, opt));} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (OutOfMemoryError e) {drawable = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;}}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> drawable;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li></ul>

4、总结

之前看过一篇文章,阿里手机淘宝客户端对存到Ashmem的图片解码做了优化,在工作线程中对图片真正解码,从而避免在UI线程渲染图片时解码,同时锁住Ashmem内存,防止在系统内存紧张时回收出现二次解码。

再者,针对资源图片,目前出现了SVG矢量图代替常规PNG图片的解决方案,但也仅仅限于线条简易的Icon图。

对于图片处理这一块,需要学习和研究的方面太多,路漫漫其修远兮,吾将上下而求索!

Android应用性能优化系列视图篇——隐藏在资源图片中的内存杀手相关推荐

  1. Android App 性能优化系列结语篇

    Android App 性能优化系列结语篇 原文出处:http://gold.xitu.io/post/581f4ad667f3560058a33057 关于Android App的优化, 从第一篇的 ...

  2. Android应用性能优化系列逻辑篇——线程相关性能优化

    线程优化是Android性能优化中一个非常重要的部分,作为进程中逻辑处理调度的基本单位,如果使用不当,非常容易造成系统资源的浪费,从而导致应用性能出问题.在日常开发中,最常出现的问题主要有两个方面,一 ...

  3. Android App性能优化系列

    Android App性能优化系列 关于Android App的优化,从第一篇的计划开始,到内存优化的系列文结束,不知不觉近三个月的时间,写了十五六篇相关的博文,算是对自己的知识的一个系统化,也希望能 ...

  4. Android性能优化系列总篇

    目前性能优化专题已完成以下部分: 性能优化总纲--性能问题及性能调优方式 性能优化第四篇--移动网络优化 性能优化第三篇--Java(Android)代码优化 性能优化第二篇--布局优化 性能优化第一 ...

  5. 性能优化系列第一篇——数据库性能优化

    本文章转载的Trinea大神的文章,文章原地址 http://www.trinea.cn/android/database-performance/ 性能优化之数据库优化 本文为性能优化的第一篇--数 ...

  6. [Android]ListView性能优化之视图缓存

    前言 ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题.本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流 ...

  7. Android App 性能优化总结

    Android App 性能优化系列结语篇 转发自:http://blog.lmj.wiki/2016/11/06/app-opti/app_opt_summary/#more 关于Android A ...

  8. Android性能优化系列之内存优化

    在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,上篇博客,我介 ...

  9. Android性能优化系列 + Android官方培训课程中文版

    Android性能优化典范 - 第6季 http://hukai.me/android-performance-patterns-season-6/ Android性能优化典范 - 第5季 http: ...

最新文章

  1. 不需要显示地图 就获得用户当前经纬度 超简单的方法
  2. JVM内存GC的骗局
  3. MySQL-性能优化_大表和大事务的常用处理方案
  4. 4.50Nginx负载均衡
  5. 02- Image Terminology
  6. thinking-in-java(14)类型信息
  7. Linux 内核中的宏定义
  8. springboot入门书籍推荐,“最粉嫩
  9. CountDownLatch使用解说
  10. Hibernate N+1 问题
  11. 揭秘springboot集成tomcat原理
  12. nginx源码下载、编译和安装
  13. RANSAC算法筛选匹配点
  14. Android开发——Kotlin语法之Lambda表达式
  15. 不同tab页sessionStorage共享情况
  16. 自行车平衡分析和控制-转载
  17. 《影响力》 -- 人类的心理行为模式
  18. C/C++ 文件设备操作之CreateFile、ReadFile和WriteFile
  19. 常用签名方式生成sign
  20. 如何获取百度地图API

热门文章

  1. Leetcode(24)——两两交换链表中的节点
  2. DataGrip连接Mysql报08S01解决方案
  3. java学不下去能学web安全吗,这半年学习 Web 安全的一点心得体会
  4. 高位在前低位在后是啥意思_详解MACD指标的死叉卖点:低位死叉+高位死叉+零轴附近死叉...
  5. Linux环境下安装tomcat并配置开机自启
  6. iphone内存管理(一)
  7. 有利于排名的网页标题和描述创作
  8. 高速公路ETC卡签之我见4-卡签结构说明
  9. 服务器 exe文件,服务器无故生成exe文件,套路有点深
  10. Tesseract怎么识别中文