~Android Studio提供了一个Android

官方教程

  1. Android
    Performance
    是 GOOGLE 近期揭晓以 Udacity 上之法定教程
    无便民是上网的同桌可以自本人之百度网盘里下载。
  2. Android Performance
    Patterns
    是 GOOGLE 在 2015 开春发布在 Facebook 上之专题课程
    立马片情节
    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 创立者 Paul Graham 高度评价 Lisp
语言,认为编程语言发展至今天,还是不曾跳出 Lisp 语言在达到世纪 60
年代所倡导之那些理念。并且,他还把好当初创业,实现财务自由之门类
Viaweb 的成归功给 Lisp 语言。详细而看 Paul Graham
的旋即首博客和立首博客。

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实例的泄漏,接下的题目修复也就算显相对简单了,就无以赘叙了。