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

2016-09-26  本文已影响138人  trampcr

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

一. 垃圾回收机制


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

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

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

二. Android中的垃圾回收机制


新生代

老年代

永久代

三. 内存泄露


四. 内存抖动


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

五. 工具


Memory Monitor

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

特点:

Allocation Tracker

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

特点:

Heap Viewer

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

特点:

六. 实例


这里有一个存在内存泄露的例子,下载地址: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

上一篇 下一篇

猜你喜欢

热点阅读