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

作者:jcmp      发布时间:2021-04-23      浏览量:0
一、一. 垃圾回收机制自动管理内存和回收

一、一. 垃圾回收机制

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

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

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出现内存泄露,如下图:

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

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1testCompile '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 Allocator,然后点击最外圈的绿色,然后双击右面的Activity,把Activity展开后会发现进行很多Rect和StringBuilder对象的创建。

问题就在这里,看代码:

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:

MemoryBugs 请注意查看MemoryMonitor 以及AllocationTracker 请注意查看通知栏LeakMemory Hello World!

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

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

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

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

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