Android应用性能优化——内存优化(内附一个内存泄露优化实例)

当我们刚开始接触Android时,可能关注的比较多的是如何实现某个功能,但学到一定程度的时候,我们会发现无论一个应用多么炫酷,如果运行特别慢,或者说很耗内存,这将会带来很差的用户体验,所以说,性能优化变得尤为重要。

一. 垃圾回收机制


自动管理内存和回收机制,垃圾回收器负责回收程序中已经不使用,但是仍然被各种对象占用的内存,将程序员从繁重、危险的内存管理工中解放出来。

缺点:可能会占用大量资源。

Android有垃圾回收机制,无需手动管理内存,Android系统会自动跟踪所有对象,并释放那些不再使用的对象。

二. Android中的垃圾回收机制


新生代

  • 大多数新建的对象都位于Eden区。
  • 当Eden区域被对象填满时,就会执行Minor GC,并把所有存活下来的对象转移到其中一个survivor区。
  • Survivor Space:S0、S1有两个,存放每次垃圾回收后存活的对象。
  • Minor GC同样会检查survivor区存活下来的对象,并把它们转移到另一个survivor区,这样在一段时间内总是有一个空的survivor区。

老年代

  • 存放长期存活的对象和经过多次Minor GC后,依然存活下来的对象。
  • 满了进行Major GC。

永久代

  • 存放方法区,方法区中有要加载的类信息、静态变量、final类型的常量、属性和方法信息。

三. 内存泄露


  • 应用程序分配了大量不能被回收的对象。
  • 系统可分配内存越来越少。
  • 新对象的创建需要内存不够。
  • GC之后再分配。
  • 60fps。

四. 内存抖动


因为在短时间内大量的对象被创建又马上被释放,瞬间产生大量的对象会严重占用新生代的内存区域,当达到阈值,剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收,即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC,这个操作又可能会影响到帧率,并使得用户感知到性能问题。

五. 工具


Memory Monitor

蓝色部分表示使用内存,灰色部分表示空闲内存,峰值表示发生了一次垃圾回收。

特点:

  • 方便显示内存使用和GC情况。
  • 快速定位卡顿是否和GC有关。
  • 快速定位Crash是否和内存占用过高有关。
  • 快速定位潜在的内存泄露问题。
  • 简单易用。
  • 不能准确定位问题。

Allocation Tracker

跟踪对象内存分配的工具。可以追踪应用程序在运行时所有已分配的内存,所有已创建的对象,对象的数量和他们所占用的内存大小以及这些对象是在哪些方法中创建的,用于检测内存抖动现象。

特点:

  • 定位代码中分配的对象的类型、大小、时间、线程、堆栈等信息。
  • 定位内存抖动问题。
  • 配合Heap Viewer一起定位内存抖动问题。
  • 使用复杂。

Heap Viewer

实时展示应用程序运行时所有已分配的对象的数量、大小以及类型信息。用于检测内存泄露。

特点:

  • 内存快照信息。
  • 每次GC之后收集一次信息。
  • 查找内存泄露利器。
  • 使用复杂。

六. 实例


这里有一个存在内存泄露的例子,下载地址:https://github.com/lzyzsd/MemoryBugs

主要使用MemoryMonitor, AllocationTracker,HeapDump以及LeakCanary等工具来查找潜在的内存问题,并尝试解决。

解决过程记录如下:

运行该程序,可以看到主界面如下图所示:

主界面

有一个TextView,一个半圆,两个按钮。

这里先点击第一个按钮StartActivityB,这时会弹出一个Toast:请注意查看通知栏LeakMemory,点开通知栏的通知,看到有提示MainActivity has leaked,意思就是MainActivity出现内存泄露,如下图:

MainActivity has leaked 1

通过分析,是由于static类型的sTextView引用了mContext导致了MainActivity发生了内存泄漏,看到这里很多人估计会一脸懵逼,难道手机会自带检测内存泄露的工具吗?其实不是,看程序源代码,不难发现在build.gradle中引入了一个叫LeakCanary的工具,具体代码如下:

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1

并且在MyApplication中LeakCanary.install(this);

由于static类型的变量是不会被垃圾回收的,所以导致了MainActivity的内存泄露,解决方案就是去掉static,修改代码:

//    private static TextView sTextView;    
      private TextView mTextView;

接着看一下半圆的绘制是否存在问题,先看代码:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    RectF rect = new RectF(0, 0, 100, 100);
    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStrokeWidth(4);
    canvas.drawArc(rect, 0, 180, true, paint);
}

果然有问题,由于onDraw()方法调用比较频繁,所以一般尽量避免在onDraw()方法中创建对象,这里恰恰就在onDraw()方法中创建对象,所以这里的修改方案是把创建对象放到定义成员变量的位置。代码如下:

private RectF mRectF = new RectF(0, 0, 100, 100);
private Paint mPaint = new Paint();

这时的onDraw()方法如下:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(Color.RED);
    mPaint.setStrokeWidth(4);
    canvas.drawArc(mRect, 0, 180, true, mPaint);
}

OK,再次运行程序,点击按钮StartActivityB,没有出现LeakCanary的提示。

在Android Studio中打开Android Monitor -> Memory,不断点击按钮StartAllocation,不断的发生内存回收和分配,会出现以下状况,这就是我们上边所说的内存抖动。

内存抖动

配合Allocation Tracking,在内存抖动开始时点击Start Allocation Tracking按钮,在抖动结束后再点击一下。会得到如下图所示的.alloc文件:

Group by Method

选择Group by Allocator,然后点击最外圈的绿色,然后双击右面的Activity,把Activity展开后会发现进行很多Rect和StringBuilder对象的创建。

Group by Allocator

问题就在这里,看代码:

private void startAllocationLargeNumbersOfObjects() {    
Toast.makeText(this, "请注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
    for (int i = 0; i < 10000; i++) {
        Rect rect = new Rect(0, 0, 100, 100);
        System.out.println("-------: " + rect.width());
    }
}

可以看到在for循环中一直创建对象及字符串的拼接。

修改方案是把Rect对象的创建放到成员变量中,在onCreate中进行初始化,为了避免在logcat输出时产生大量的String对象,修改方案是在onCreate中把String对象创建好,这样就不会重复创建了,还要把里面的字符串提取出来,放到strings.xml中,有的要设置为static final类型的字符串资源,修改代码如下:

成员变量:

public static final String LINE_TAG = "-------: ";
private Rect mRect;
private String mLogString;

onCreate():

mRect = new Rect(0, 0, 100, 100);
mLogString = LINE_TAG + mRect.width();

startAllocationLargeNumbersOfObjects()

private void startAllocationLargeNumbersOfObjects() {
    Toast.makeText(this, R.string.memory_monitor, Toast.LENGTH_SHORT).show();
    for (int i = 0; i < 10000; i++) {
        System.out.println(mLogString);
    }
}

strings.xml:

<resources>
    <string name="app_name">MemoryBugs</string>
    <string name="memory_monitor">请注意查看MemoryMonitor 以及AllocationTracker</string>
    <string name="leakmemory">请注意查看通知栏LeakMemory</string>
    <string name="hello_world">Hello World!</string>
</resources>

以上解决了三个问题,那么怎么检测是否还存在内存泄露呢?还有一个工具叫Heap Viewer,这个工具可以实时展示应用程序运行时所有已分配的对象的数量、大小以及类型信息,可以检测内存泄露。

在手机屏幕上点击StartActivityB,在Android Studio中点击Dump Java Heap,选择Package Tree View,找到我们的程序,可以看到MainActivity还没有被垃圾回收。

Heap Viewer.png

手动进行一下垃圾回收,再次点击Dump Java Heap,可以看到如下效果:

GC之后_Heap Viewer.png

这时看到MainActivity已经被垃圾回收了,不存在内存泄漏问题了。

参考:http://www.jianshu.com/p/c53101db112e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容