前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android应用性能优化——内存优化(内附一个内存泄露优化实例)

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

作者头像
trampcr
发布2018-09-28 15:49:02
1.3K0
发布2018-09-28 15:49:02
举报
文章被收录于专栏:7号代码7号代码

当我们刚开始接触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的工具,具体代码如下:

代码语言:javascript
复制
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,修改代码:

代码语言:javascript
复制
//    private static TextView sTextView;    
      private TextView mTextView;

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

代码语言:javascript
复制
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()方法中创建对象,所以这里的修改方案是把创建对象放到定义成员变量的位置。代码如下:

代码语言:javascript
复制
private RectF mRectF = new RectF(0, 0, 100, 100);
private Paint mPaint = new Paint();

这时的onDraw()方法如下:

代码语言:javascript
复制
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

问题就在这里,看代码:

代码语言:javascript
复制
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类型的字符串资源,修改代码如下:

成员变量:

代码语言:javascript
复制
public static final String LINE_TAG = "-------: ";
private Rect mRect;
private String mLogString;

onCreate():

代码语言:javascript
复制
mRect = new Rect(0, 0, 100, 100);
mLogString = LINE_TAG + mRect.width();

startAllocationLargeNumbersOfObjects()

代码语言:javascript
复制
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:

代码语言:javascript
复制
<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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.09.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 垃圾回收机制
  • 二. Android中的垃圾回收机制
    • 新生代
      • 老年代
        • 永久代
        • 三. 内存泄露
        • 四. 内存抖动
        • 五. 工具
          • Memory Monitor
            • Allocation Tracker
              • Heap Viewer
              • 六. 实例
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档