Android Studio提供了贰个Android

官方教程

  1. Android
    Performance

    是 GOOGLE 近日发布在 Udacity 上的官方教程
    不便宜科学上网的同班能够从自己的百度网盘里下载。
  2. Android Performance
    Patterns

    是 GOOGLE 在 二零一四 开春表露在 照片墙 上的专题课程
    那部分内容
    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 的1个插件,也有 stand
    alone

    的工具得以下载使用。

多少个规范

  • 别在循环里分配内部存储器 (创设新对象)
  • 尽大概别在 View 的 onDraw 函数里分配内部存款和储蓄器
  • 实际上没辙防止在那一个场景里分配内部存款和储蓄器时,考虑选用对象池 (Object Pool)

2 预备知识

八个简单的实例

内部存款和储蓄器抖动

经过1个万分简单的例证来演示内部存款和储蓄器抖动。那个例子里,在自定义 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

那三个区域都有稳定的深浅,随着新的指标陆续被分配到此区域,当那些目的总的大小快达到该区域的高低时,会触发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 对两份 HPRADOPOF 文件实行分析,结合代码找出或许存在的内部存款和储蓄器泄漏

例如对准拨号盘越来越慢的标题,大家得以开机后开发银行拨号盘,打进打出13个电话。然后抓个
HPROF 文件。接着,再打进打出13个电话,再抓二个 HPROF
文件。接着拿那五个公文相比较分析,看是否会导致电话打进打出越来越多,内部存款和储蓄器占用越来越多的场馆时有产生。

!!! notes “HPROF文件”
HPROF 不难地精晓,正是从 jvm 里 dump 出来的内部存款和储蓄器和 CPU
使用情形的八个二进制文件。它的英文全名叫 A Heap/CPU Profiling
Tool。这里有它全部的合法文书档案和它的野史介绍。

打开 MAT 后,会有三个 Tutorials
来教我们怎么用。那里列出多少个操作步骤及其注意事项。

  • 在 DDMS 里导出 HPROF 文件前,最佳手动执行一下
    GC。指标是让导出的内存全部是被引述的。否则在做内存占用相比时,会有众多不须求的内部存款和储蓄器占用被标识出来,烦扰我们开展辨析。
  • 展开比较时,最棒是选拔操作较多的和操作较少的对待,这样得出的 delta
    是正数
  • 经过对照,发现内部存款和储蓄器泄漏时,能够用 OQL 来询问,并由此 Root to GC
    功效来找到产生泄漏的源代码

在我们的演示程序里面,每一回点击 Settings
菜单,都会造成3次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
出来的内部存款和储蓄器里,确实有不少个这个类的实例。在图上右击任何2个实例,右击,采取
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个“破案”的工具给大家。我们使用那些工具,倒回来去发现代码里的难点。

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 是在 一九五九 年由 John McCarthy 发明的,此表达是为着消除 Lisp
编制程序语言里的内部存款和储蓄器难点的。《黑客和美术大师》笔者,硅谷最有影响力的孵化器企业YC 成立者 保罗 格拉汉姆 中度评价 Lisp
语言,认为编制程序语言发展到近年来,照旧尚未跳出 Lisp 语言在上世纪 60
时期所倡导的那多少个理念。并且,他还把本人那时创业,实现财务自由的项目
Viaweb 的打响归功于 Lisp 语言。详细可观看 Paul 格拉汉姆的那篇博客那篇博客

4 常见内部存款和储蓄器品质难点模拟及优化

4.1 内部存款和储蓄器抖动现象模拟及优化

内部存款和储蓄器抖动是因为在长期内大气的靶子被创制又立时被保释导致的。由此下边包车型大巴事例中本人通过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@31507一九五四,因而TestLeakActivity3$1是TestLeakActivity3的当中类。
4>
target和handler是同贰个实例(TestLeakActivity3$1@316570880),并且handler是TestLeakActivity3@315071955的一个属性。
由地点的4条新闻方可汲取只要TestLeakActivity3中handler的生命周期在TestLeakActivity3生命周期之内,就足以幸免TestLeakActivity3实例的透漏,接下去的难题修复也就展现相对不难了,就不在赘叙了。

相关文章