Android Studio提供了一个Android

合法教程

  1. Android
    Performance

    是 GOOGLE 近日发表在 Udacity 上的官方教程
    不便宜科学上网的同室可以从自家的百度网盘里下载。
  2. Android Performance
    Patterns

    是 GOOGLE 在 2015 年终发表在 非死不可 上的专题课程
    这有的情节
    CDGChina
    加了中文字幕,并放在
    Youku
    上了。

!!! notes
总的来说 Android 生态圈的习性和电量消耗等问题,已经严重到让 Google不得不重视的境界啦 ~~

1 概述

本篇博客是Android
App性能优化
专题的第一篇随笔,该专题会在渲染、总计、内存、电量方面展开讲解Android
App的性能优化。

为了进一步高效的优化App性能,Android Studio提供了一个Android
Monitor工具,它身处Android Studio主窗口的江湖,Android
Monitor提供了实时记录和观望App以下信息的工具:

  • 系统或用户定义的Log音讯
  • Memory,CPU和GPU使用率
  • Network流量(仅限硬件设备)

关于内存的多少个理论知识

GC 的做事体制
当 GC 工作时,虚拟机截至任何工作。频繁地触发 GC
举办内存回收,会导致系统性能严重下降。

内存抖动
在极短的岁月内,分配大量的内存,然后又释放它,这种景观就会导致内存抖动。典型地,在
View 控件的 onDraw
方法里分配大量内存,又释放大量内存,这种做法极易滋生内存抖动,从而造成性能降低。因为
onDraw 里的大度内存分配和释放会给系统堆空间造成压力,触发 GC
工作去自由更多可用内存,而 GC 工作起来时,又会吃掉宝贵的帧时间 (帧时间是
16ms) ,最后致使性能问题。

内存泄漏
Java 语言的内存泄漏概念和 C/C++ 不太相同,在 Java
里是指不得法地引用导致某个对象不可能被 GC
释放,从而造成可用内存越来越少。比如,一个图形查看程序,使用一个静态 Map
实例来缓存解码出来的 Bitmap
实例来增速加载进度。这些时候就可能存在内存泄漏。

内存泄漏会导致可用内存越来越少,从而致使频繁触发 GC
回收内存,进而导致性能降低。

调剂工具

  • Memory Monitor Tool: 可以查阅 GC 被触发起来的年月体系,以便阅览 GC
    是否影响属性。
  • Allocation Tracker Tool: 从 Android Studio
    的那个工具里查看一个函数调用栈里,是否有大气的一模一样档次的 Object
    被分配和刑满释放。假若有,则其可能滋生性能问题。
  • MAT: 这是 Eclipse 的一个插件,也有 stand
    alone

    的工具得以下载应用。

多少个原则

  • 别在循环里分配内存 (创立新目的)
  • 尽心尽力别在 View 的 onDraw 函数里分配内存
  • 骨子里没辙制止在这么些场景里分配内存时,考虑选取对象池 (Object Pool)

2 预备知识

多少个大概的实例

内存抖动

经过一个异常简单的事例来演示内存抖动。这些例子里,在自定义 View 的
onDraw 方法里大量分红内存来演示内存抖动和性质之间的关联。

版本一:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String msg = "";
        for (int i = 0; i < 500; i++) {
            if (i != 0) {
                msg += ", ";
            }
            msg += Integer.toString(i + 1);
        }
        Log.d("DEBUG", msg);
    }

版本二:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 500; i ++) {
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(i + 1);
        }
        Log.d("DEBUG", sb.toString());
    }

内存抖动的性状:

从 Memory Monitor 来看,有毛刺出现。即短期内分配大量的内存并触发 GC。

图片 1

memory_churn

从 Allocation Tracker 里看,四次操作会有大量的内存分配爆发。

图片 2

memory_tracker

内存泄漏

本条事例里,我们简要地让点击 Settings 菜单,就生出一个 100KB
的内存泄漏。

    private void addSomeCache() {
        // add 100KB cache
        int key = new Random().nextInt(100);
        Log.d("sfox", "add cache for key " + key);
        sCache.put(key, new byte[102400]);
    }

内存泄漏的表征:

从 Memory Monitor 来看,内存占用越来越大

图片 3

memory_tracker

利用
MAT
工具举行标准分析。这是个很大的话题。几乎可以独立成多少个章节来讲。可以参见
MAT 本身自带的 Tutorials
来学学。另外,这篇著作里的分析方法是个科学的上马。

演示代码应用 Android Studio
开发环境,可以从这里下载。

2.1 Android App的内存结构

Random Access
Memory(RAM)在任何软件开发环境中都是一个很名贵的资源。这点在大体内存平日很简单的运动操作系统上,显得更为出色。系统会在RAM上为App进程分配一定的内存空间,然后该App进程就会运作在该内存空间上。该内存空间会被分成Stack内存空间和Heap内存空间,其中Stack内存空间里存放对象的引用,Heap内存空间里存放对象数据。

在Android的尖端系统版本里面针对Heap内存空间有一个3级的Generational Heap
Memory的模子,它包括Young Generation,Old Generation,Permanent
Generation五个区域。最新分配的靶子会存放在Young
Generation区域,当这一个目的在这么些区域停留的时日领先某个值的时候,会被挪动到Old
Generation,最终到Permanent Generation区域。整个结构如下图所示:

图片 4

这3个区域都有固定的高低,随着新的对象陆续被分配到此区域,当那些目的总的大小快达到该区域的大大小时辰,会触发GC的操作,以便腾出空间来存放其他新的目的,如下图所示:

图片 5

日前刚分配的靶子会放在Young
Generation区域,这么些区域的GC操作速度也是比Old
Generation区域的GC操作速度更快的,如下图所示:

图片 6

平时情形下,当GC线程运行时,其他线程会暂停工作(包括UI线程),直到GC完成,如下图所示:

图片 7

虽然如此单个的GC操作并不会占据太多日子,可是反复的GC操作有可能会潜移默化到帧率,导致卡顿。

拔取 MAT 分析内存问题

内存泄漏

一个天下无双的题材是 Android
系统越用越慢。这种至高无上地是由内存泄漏引起的。一个很有用的缓解这种问题的措施是:相比较前后多个级次的内存的施用境况。一般流程如下:

  1. 利用 ddms 工具 dump HPROF file
  2. 接纳 hprof-conv 把 dalvik 格式的更换为普通 jvm 格式
  3. 双重步骤 1 和 2 抓出两份 LOG。
  4. 运用 MAT 对两份 HRPOF 文件举行辨析,结合代码找出可能存在的内存泄漏

比如针对拨号盘越来越慢的问题,我们得以开机后启动拨号盘,打进打出10个电话。然后抓个
HPROF 文件。接着,再打进打出10个电话,再抓一个 HPROF
文件。接着拿这多少个文本相比分析,看是不是会招致电话打进打出越多,内存占用越多的情状时有暴发。

!!! notes “HPROF文件”
HPROF 简单地领略,就是从 jvm 里 dump 出来的内存和 CPU
使用状态的一个二进制文件。它的英文全名叫 A Heap/CPU Profiling
Tool。这里有它完整的合法文档和它的历史介绍。

开辟 MAT 后,会有一个 Tutorials
来教我们怎么用。这里列出几个操作步骤及其注意事项。

  • 在 DDMS 里导出 HPROF 文件前,最好手动执行一下
    GC。目标是让导出的内存全部是被引用的。否则在做内存占用比较时,会有众多不必要的内存占用被标识出来,搅扰大家开展辨析。
  • 举办对照时,最好是挑选操作较多的和操作较少的争持统一,这样得出的 delta
    是正数
  • 通过对照,发现内存泄漏时,可以用 OQL 来查询,并透过 Root to GC
    效用来找到暴发泄露的源代码

在我们的以身作则程序里面,每一趟点击 Settings
菜单,都会促成两次100KB的内存泄漏。下边是我们应用方面介绍的流程来寻觅内存泄漏问题。我们先点击
5 次 Settings 菜单,然后手动触发一回 GC,再导出 HPROF
文件。接着,大家再点击 6 次 Settings 菜单,然后手动触发一遍GC,再导出第二份 HPROF 文件。我们拿这两份 HPROF 就足以做一些对照。

图片 8

mat_diff.png

经过上图可以见到,五遍操作确实导致了少数类的实例扩张了。图中得以领略地看来
byte[] 和 java.util.HashMap$HashMapEntry
多少个类扩大得相比较强烈。这样,我们无论选用一个,通过 OQL
来询问系统中的这多少个内存。

图片 9

mat_qql.png

从上图可以找到,本次 dump
出来的内存里,确实有很两个这多少个类的实例。在图上右击任何一个实例,右击,选拔
Paths to GC roots,可以找到那个实例是被何人引用的。

图片 10

mat_gc_root.png

从上图可以看出来,这多少个内存是被 MainActivity 里的 sCache
引用的。通过翻阅代码,我们就足以找到这多少个漏洞了。即每一遍都往 sCache
里保存一个引用。

2.2 GC root and Dominator tree

Java中有以下二种GC root:

  • references on the stack
  • Java Native Interface (JNI) native objects and memory
  • static variables and functions
  • threads and objects that can be referenced
  • classes loaded by the bootstrap loader
  • finalizers and unfinalized objects
  • busy monitor objects

假定从GC Root到达Y的的所有path都经过X,那么大家称X dominates
Y,或者X是Y的Dominator
tree。当优化内存时,能够通过释放一个dominator对象来刑释解教其有着下级对象。
例如,在下图中,尽管要去除对象B,那么也会自由其所主导的目的所采用的内存,即对象C,D,E和F,实际上,倘若目标C,D,
E和F被标记为除去,但目的B依旧指向它们,这说不定是它们未被放飞的原故。

图片 11

总结

Google视频介绍的情节是硬知识,理解这么些文化可以帮忙大家写出高质料,高性能的代码。而
MAT, HPROF, Memory Monitor, Allocation Tracker
提供了一个“破案”的工具给我们。大家选用这一个工具,倒回来去发现代码里的题目。

3 Memory Monitor

Android Monitor提供了Memory
Monitor工具,以便更轻松地实时监听App的特性和内存使用处境,通过该工具得以:

  • 显示空闲和已分配的Java内存随时间变更的图纸。
  • 乘势时光的延迟显示垃圾回收(GC)事件。
  • 启动GC事件。
  • 高速测试UI线程卡顿是否与一再GC事件有关。
    当GC线程运行时,其他线程都会中断(包括UI线程),直到GC完成。频繁GC操作有可能会潜移默化到帧率,导致卡顿,特别是性质相比较差的手机上,尤为引人注目。
  • 高速测试app崩溃是否与内存不足(内存溢出或者内存泄漏)有关。

Memory Monitor的工作流程
为了分析和优化内存使用,典型的办事流程是运行app并执行以下操作:

  1. 应用Memory Monitor来分析是否由于糟糕GC事件情势招致的app性能问题。
  2. 一旦在短期内暴发频繁的GC事件,就由此Dump Java
    Heap操作来查阅当前内存快照,继而查找哪些项目的目的有可能爆发了内存泄漏或者占有了太大内存。
  3. 说到底经过Start allocation
    tracking操作来追踪对象分配内存时对应的办法调用。

在Memory Monitor中执行Dump Java Heap操作时,会创立一个Android-specific
Heap/CPU Profiling (HPROF)文件,HPROF文件中保留了app该时刻内存中的GC
root列表,文件创制完成后会自动在HPROF Viewer中开拓, HPROF Viewer使用

图片 12

图标标示GC root(深度为零)以及使用

图片 13

图标标示Dominator tree。

延长阅读

至于 Android 性能优化,网络上有几篇相比较好的小说,基本听从 GOOGLE
的法定教程翻译过来的,质地相比高。可以参考一下。

  1. Android
    性能优化内存篇
    胡凯的博客
  2. Android性能优化典范胡凯的博客

冷知识

GC 是在 1959 年由 约翰(John) 麦卡锡(McCarthy) 发明的,此表明是为了化解 Lisp
编程语言里的内存问题的。《黑客和歌唱家》笔者,硅谷最有影响力的孵化器企业YC 创造者 保罗 格雷汉姆(Graham) 中度评价 Lisp
语言,认为编程语言发展到现在,仍然不曾跳出 Lisp 语言在上世纪 60
年代所倡导的这些理念。并且,他还把团结这时创业,实现财务自由的花色
Viaweb 的中标归功于 Lisp 语言。详细可观看 保罗 格雷厄姆的这篇博客这篇博客

4 常见内存性能问题模拟及优化

4.1 内存抖动现象模拟及优化

内存抖动是因为在短期内大量的对象被创设又即刻被假释导致的。因而上边的例子中自我透过一个for循环来不断的创始和刑满释放对象来模拟内存抖动的情形。
举个例证:

public class TestLeakActivity1 extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button click = new Button(this);
        click.setOnClickListener(this);
        click.setText("模拟内存抖动");
        setContentView(click);
    }

    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    Bitmap result1;
                    result1 = BitmapFactory.decodeResource(getResources(), R.drawable.noah_silliman);
                }
            }
        }).start();
    }
}

上边代码很简单,当点击仿照内存抖动按钮时,通过Memory
Monitor工具得以看出出现了卓殊显著的内存抖动情况,如下图所示:

图片 14

当内存抖动的峰值快达到Young
Generation区域的容量时就会触发GC操作,因而为了触发GC操作,就在代码中加载来一张相当大图片(5184*3456),对应的GC
log如下:

08-22 10:53:51.579 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 14(608B) AllocSpace objects, 1(68MB) LOS objects, 39% free, 17MB/29MB, paused 492us total 52.970ms
08-22 10:53:51.988 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 14(608B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 370us total 36.902ms
08-22 10:53:52.329 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 14(608B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 365us total 36.754ms
08-22 10:53:52.664 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 14(608B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 305us total 32.072ms
08-22 10:53:52.952 11758-11758/com.cytmxk.test I/art: WaitForGcToComplete blocked for 8.791ms for cause Alloc
08-22 10:53:52.988 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 8(304B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 305us total 32.178ms
08-22 10:53:53.396 11758-11758/com.cytmxk.test I/art: WaitForGcToComplete blocked for 9.036ms for cause Alloc
08-22 10:53:53.444 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 8(304B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 493us total 43.976ms
08-22 10:53:53.809 11758-11758/com.cytmxk.test I/art: WaitForGcToComplete blocked for 11.791ms for cause Alloc
08-22 10:53:53.853 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 8(304B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 373us total 38.598ms
08-22 10:53:54.181 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 14(608B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 311us total 32.794ms
08-22 10:53:54.617 11758-11758/com.cytmxk.test I/art: Alloc partial concurrent mark sweep GC freed 18(736B) AllocSpace objects, 1(68MB) LOS objects, 40% free, 17MB/29MB, paused 481us total 46.280ms

经过下边的log中的时间点注明了发出了累累的GC,
由于导致GC的缘由是Alloc(可以参照调研 RAM
使用状态
来理解GC
Log),因而恐怕会在不久的将来会发出OOM卓殊;当自身点击一次按钮时,确实引发OOM相当;频繁GC操作有可能会潜移默化到帧率,导致卡顿。

Allocation Tracker效用(可以参考Allocation
Tracker
)对于识别和优化内存抖动是丰盛实用的,接下去就经过这么些效用来恒定方面暴发内存抖动的职务:

图片 15

点击右下角肉色框中的按钮,即起来执行Allocation
Tracker,等一段时间,再点击一下右下角红色框中的按钮就会截至Allocation
Tracker,此时下边的波浪图中矩形区域就是Allocation
Tracker执行的周期,并且会转变和开辟一个alloc格式的文本,通过上图可知分配内存最多的是Thread
22线程,打开Thread
22线程的调用stack,定位到TestLeakActivity1的34行就是分配内存的地点,接下去的题材修复也就显得相对简单了,尽量避免在for循环里边分配对象,尝试把目标的成立移到循环体之外,对于这多少个不可能防止需要创立对象的境况,我们可以设想对象池模型,通过对象池来解决频繁创设与销毁的题材,注目的在于对象池没用时需要手动释放对象池中的对象。

4.2 内存泄漏现象模拟及优化

内存泄漏是指不再使用的目的由于被张冠李戴引用而一筹莫展被GC回收,这样就造成那个目标一贯留在内存当中,占用了难得的内存空间。显然会导致每级Generation的内存区域可用空间变小,GC就会更便于被触发,从而引起性能问题。

举个例子:

public class TestLeakActivity2 extends AppCompatActivity implements View.OnClickListener {

    private Button testLeakBtn = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_leak2);
        testLeakBtn = (Button) findViewById(R.id.button_test_leak);
        testLeakBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, TestLeakActivity3.class);
        startActivity(intent);
    }
}

public class TestLeakActivity3 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ImageView imageView = new ImageView(this);
        imageView.setImageResource(R.drawable.noah_silliman);
        setContentView(imageView);
        handler.sendEmptyMessageDelayed(0, 60000);
    }

    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

地点的代码很简单,运行App,多次并且快捷执行操作(从TestLeakActivity2跳转到TestLeakActivity3,然后再回来TestLeakActivity2),接着利用Memory
Monitor工具的Dump Java Heap效率(可以参见HPROF Viewer and
Analyzer
)列举此时Heap中各样类型对象的有些和分寸:

图片 16

点击右上角的箭头,可以分析出当下泄漏的activity,然后选中instance窗口中的第一个泄漏的实例,下边的reference
tree窗口就会顿时突显该实例对应的reference tree,可以见见:
1>
TestLeakActivity3$1@316570880是TestLeakActivity3@315071952的Dominator
tree。
2>
TestLeakActivity3$1@316570880的门类是Message中target的门类,即Handler类型。
3>
由于TestLeakActivity3$1@316570880由此this$0引用TestLeakActivity3@315071952,由此TestLeakActivity3$1是TestLeakActivity3的其中类。
4>
target和handler是同一个实例(TestLeakActivity3$1@316570880),并且handler是TestLeakActivity3@315071952的一个特性。
由地方的4条信息方可汲取只要TestLeakActivity3中handler的生命周期在TestLeakActivity3生命周期之内,就可以制止TestLeakActivity3实例的透漏,接下去的题目修复也就体现相对简单了,就不在赘叙了。