背景
Android手机不像pc等设备拥有大量的内存,因此内存成为Android中非常稀缺的资源。开发者在开发应用时,要特别关注应用的内存使用情况。内存吃紧会引发系统gc,频繁gc会造成卡顿,申请内存过多甚至超过最大限制时会oom,当一个使用内存过多的应用不在前台时,被系统强制回收的概率也会增加。
内存最大值
Android为每个应用创建单独的jvm进程,为了不让其中的一个应用消耗过多的内存资源,每个jvm进程所能申请的内存有最大限制。该值受机型分辨率,最大内存等因素影响,最终记录在/system/build.prop文件中,可以通过查看该文件了解jvm进程所能申请的最大内存。
cat /system/build.prop | grep dalvik
dalvik.vm.heapgrowthlimit=256m
dalvik.vm.heapsize=512m
heapgrowthlimit为普通应用所能申请的最大内存,也可以用ActivityManager.getLargeMemoryClass() 方法得到。
而当AndroidManifest.xml 的Application标签android:largeHeap=”true” 时,所能获取的最大内存为heapsize。
内存分配与GC
图片—内存大户
图片资源是可以说是内存中的大户,一个应用无论是图标,背景都和Drawable, Bitmap等相关,哪怕采用svg格式,最终也是通过绘成drawable来完成界面的渲染。那我们就有必要好好了解下图片加载到内存中到底有多大了。
内存中的图片有多大?
计算图片加载到内存中的大小,要看图片是以哪种格式加载进去的。如下表所示:
格式 | 大小 (byte) |
---|---|
Alpha_8 | 1 |
RGB_565 | 2 |
ARGB_4444(不推荐) | 2 |
ARGB_8888 | 4 |
Alpha_8 只有透明度
RGB_565 没有透明度
ARGB_8888 有透明度
现在我们有一张图片,它被加载到内存后是300 * 400的大小,格式为RGB_565,那它有多大呢?
size = 300 * 400 * 2 / 1024 = 234.375(kb)
那我们来计算下如果内存中加载了一张网络图片,它的大小为1920*1080, 格式为ARGB_8888,那它的大小有多大呢?
size = 1920 * 1080 * 4 / 1024 / 1024 = 7.91(mb)
理论上,1920 * 1080的手机,留出3个屏幕的图片内存作为图片缓存便够了,大概24M。
图片加载到内存中有多大?
下面我们来看另一个问题,一张300 * 400 的图片,放在xx-hdpi的目录下,它被加载到内存中有多大?
Android 手机为了适配不同分辨的手机,会根据手机的屏幕密度与资源目录对应的密度比作适当的缩放。比如,我们使用的手机是1920 * 1080, 尺寸为5.5’。
那手机的屏幕密度为:
dpi = √ (1920 ^ 2 + 1080 ^ 2) / 5.5 = 400
而资源目录对应的dpi如下表:
目录 | dpi |
---|---|
drawable-ldpi | 120 |
drawable-mdpi | 160 |
drawable-hdpi | 240 |
drawable-xhdpi | 320 |
drawable-xxhdpi | 480 |
drawable-xxxhdpi | 640 |
那么这张图加载到内存中的大小:
scaledWidth = int(300 * 400 / 480f + 0.5f) = 250
scaledHeight = int(400 * 400 / 480f + 0.5f) = 333
如果它是以ARGB_8888 的格式加载到内存中,则它所占用的在大小为:
scaledWidth * scaledHeight * 4 / 1024 = 250 * 333 * 4 / 1024 = 325.19(kb)
我们要清楚地知道资源文件放在不同的地方,加载内存中是多大。
编程猫App的优化
加入编程猫之后,我看到了App的内存占用情况,惊人的198M。造成的直接问题是有些页面卡顿情况非常严重。为什么会这么大呢?能造成这么大的内存使用量,肯定是哪里出了严重的问题。看来对它的优化势在必行!
寻找内存大对象
我们利用android studio对它多次gc之后,dump出内存文件。
android studio dump 出的内存文件并非标准hprof文件, 我们需要用hprof-conv 转成标准hprof文件。
hprof-conv com.codemao.dan_2017.07.04_12.09.hprof com.codemao.dan_converted.hprof
查看hprof文件我们采用mat工具,dominator_tree列表,列出占用内存最大的对象。并使用list object -> with imcoming references
查看它被谁引用了
最后发现是SimpleDraweeView使用的图片资源,可是为什么这么大呢?找到它对应的原始图片文件,发现这张图的尺寸大小达到了2480*3507! 加载到内存中的大小达到30m之多,而且像这样的图片还有3张。造成相应的页面因为频繁gc,非常卡顿,而那个图片在屏幕的大小只有 100dp * 142dp。
项目中采用了fresco图片加载框架,但是没有做缓存策略,而服务器上的图片资源本来不是用来适配移动设备的,所以就造成图片资源非常巨大。
好的,知道原因了, 我们为fresco加上最大30m的缓存区,开启自动裁剪尺寸设置,然后,加载到内存的图片大小与SimpleDraweeView的大小一致。这样图片问题基本解决了。
无用的xml控件与资源
图片问题解决之后,内存问题己经好了很多,下降到了56M左右了。再次查看下还有其他的大内存对象吗?
发现一个drawable对象达到了2m,查看它的引用链,发现是一张从drawable-nodpi目录中加载出来的图片,尺寸达到1020 * 520, 而屏幕实际使用大小18dp * 18dp。而且更关键的是,图片是在xml中作为背景图使用的,而在代码中,又把整个控件都setVisibility(GONE)
完全不显示!
- 对于这种无用的布局,我们应该尽量删去它,一方面可以减少绘制,另一方面也可以减少不必要的资源加载。
- 资源文件的大小应该尽量与控件大小相适配,对于这点恐怕我们平时在开发的时候也会很容易忽视。
mDrawingCache
发现一个很奇怪的问题,为什么这个recyclerView 会有mDrawingCache对象,占用内存还达到4M,而其他的recyclerView却没有呢?
mDrawingCache是View里用于缓存视图的bitmap, 可调用buildDrawingCache() 生成,通过getDrawingCache方法获取视图内容。
查看代码也没有主动对这个recyclerView调用buildDrawingCache(),那它是怎么生成的呢?为了弄清这个问题,自定义了recyclerView,重写了buildDrawingCache(),打印了方法的调用栈信息。
@Override
public void buildDrawingCache(boolean auto) {
try {
throw new Exception("buildDrawingCache");
} catch (Exception ex) {
ex.printStackTrace();
}
super.buildDrawingCache(auto);
}
顺着打印的信息,找到View.java如下代码:
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
}
发现如果View开启了离屏缓存,便会在绘制的时候build出自己的bitmap,之后如果没有内容变更会直接使用缓存的bitmap来进行绘制,从而提高绘制速度。
我们找到recyclerView对应的xml,看到确实打开了缓存
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_Novel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layerType="software"
android:layout_marginLeft="18dp">
想必是因为之前的开发为了解决那个页面的卡顿问题而开启了缓存,可是并不知道卡顿的原因是因为内存吃紧下的频繁GC,而开启了缓存之后反而加剧了内存问题。将android:layerType="software"
去除就好了。
一般经常变动的View不适合开启缓存,复杂而不易变动的View适合开启。
总结
- 图片资源要注意使用缓存防止oom,加载到内存的图片大小应尽量与显示的大小一致,减少内存开销。这方面,glide能自动加载imageView大小的图片,fresco需要手动设置。
- xml里无用的布局尽量删除,避免不必要的绘制,与引用资源文件消耗内存。
最后的战果,在经过上面的优化之后,内存使用稳定在46m左右,页面流畅不卡, 成就感满满。
参考文献: