• Stars
    star
    178
  • Rank 214,989 (Top 5 %)
  • Language
    Java
  • License
    MIT License
  • Created over 5 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An automatically testing and analysis hprof library for android app (自动分析Android内存泄漏)

MMAT - Android自动化内存分析工具

Download CircleCI

English README

在开发Android App过程中我们通常使用LeakCanary进行内存泄漏检测, 它基本原理是在App运行时检测Activity、Fragment是否产生内存泄漏, 如果有内存泄漏则进行dump hprof, 然后分析该泄漏的GC ROOT, 最终通过通知栏通知用户内存泄漏的情况. 这种方式能够在开发app时简单、有效地找出内存泄漏.

但是由于LeakCanary是app运行时发现泄漏之后立即dump 内存快照,并且实时进行内存分析, 而由于移动设备的计算能力有限, 导致如果内存泄漏较多时使用LeakCanary并不能在运行时分析出所有内存泄漏,例如当LeakCanary正在分析一次内存泄漏时又产生了另外的内存泄漏, 而在LeakCanary分析完所有内存泄漏之前用户可能已经退出了app.

因此需要另外一种补充机制, 能够在App运行结束之后进行全面、自动地离线分析app内存泄漏, 一次性分析出本次App运行产生的所有Activity、Fragment的内存泄漏, 那么将会让内存泄漏分析更加全面、高效.

MMAT就是为了解决这个问题, 它的核心思路是用户在操作完app (可以是工程师自己操作,也可以通过运行Monkey进行随机操作)之后,通过adb shell命令将app退回到主页面, 然后再退回桌面 (此时应用的Application 还存在, 但是所有Activity都应该被销毁, app处于后台状态。如果此时还有Activity、Fragment实例, 那么代表产生了内存泄漏). 当App处于后台状态时dump App内存快照到pc上, 然后再通过MMAT进行离线分析, 最终得到内存泄漏的完整报告.

一、MMAT 工作流程

  1. 如果有配置monkey测试命令, 则执行monkey测试 (monkey测试会使得App会随机进入各种Activity, 这种压力测试也容易产生内存泄漏; )
    • 1.1 执行monkey测试
    • 1.2 回到app 主页面
    • 1.3 将app退到后台, 回到手机桌面
    • 1.4 通过adb shell kill -10 命令执行app的force gc (需要手机是root)
  2. 如果你不想使用monkey, 也可以自己手动操作App, 完成所有操作之后将App退到后台
  3. 运行MMAT, dump hprof内存快照
  4. 分析hprof, 得到所有Activity、Fragment泄漏的记录以及超过一定大小的Bitmap文件
  5. 将分析结果输出为html报告

MMAT Demo演示请点击下面的视频:

二、使用MMAT

注意: 因为MMAT在通过monkey操作app后会使用 adb 命令dump应用的内存信息, 因此如果你需要dump release版的app的内存信息, 请确保你的app在测试时可调试的, 即需要在 AndroidManifest.xml 的 application 节点中添加 android:debuggable="true", 请参考AndroidManifest.xml配置 (风险提示: 建议只在测试时开启debuggable=true, 对外发布的apk不要设置为true).

使用MMAT有两种方式, 一种是通过mmat gradle插件、一种是直接执行jar文件, 请参考 2.1章节2.2章节

2.1 通过 gradle plugin 使用MMAT

  • 在项目的build.gradle 中添加mmat-plugin引用;
    示例如下:
buildscript {

    repositories {
        // ...
    }
    
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'

        // add mmat plugin library
        classpath 'org.mrcd:mmat-plugin:0.9.2'
    }
}
  • 在app module 的 build.gralde 中添加mmat-plugin与配置
apply plugin: 'com.mrcd.mmat.plugin'


// 配置 mmat plugin 
mmat {
	// json config file
    jsonConfigFile 'app/mmat-config.json'
    // 是否禁用monkey测试
    disableMonkey false
    
    // 如果设定了该参数, MMAT会直接分析该内存快照,monkey脚本会被忽略
    // hprofFile "your-hprof-file-path"
}
  • 执行 ./gradlew startMmatRunner 进行app自动内存分析, 最终报告会存在被测试app的hprof_analysis/report/ 目录下, 报告示例参考Hprof Analysis Report.

2.2 通过jar文件使用MMAT

mmat-1.0.jar下载到项目的根目录, 另外在项目的根目录下添加mmat-config.json配置 (如何配置请参考mmat config配置 ), 然后执行 mmat的可执行jar文件. 例如我的测试项目路径是 /User/mrsimple/test-project/, 添加 mmat-config.json 文件,并且进行相关的配置, 最后安装要测试的apk之后, 在项目根目录下执行进入到如下命令:

java -jar mmat-1.0.jar /User/mrsimple/test-project/mmat-config.json

执行完之后即可在 /User/mrsimple/test-project/hprof-analysis/report 下看到内存分析的html报告. 如下图所示:

报告中第一行为报告的标题,第二行给出了运行测试的手机型号、系统版本等信息。再往后就是内存泄漏的记录列表,例如第一条记录为:

1. com.example.mmat.MemoryLeakActivity (0x12d552d0) instance (2.06 MB)

* leaked ==> com.example.mmat.MemoryLeakActivity (0x12d552d0) instance (2.06 MB)
  item -> java.util.LinkedList$Node
    first -> java.util.LinkedList
		sActivityLeaked -> static com.example.mmat.MemoryLeakActivity
			[15] -> array java.lang.Object[]
				runtimeInternalObjects -> dalvik.system.PathClassLoader
					contextClassLoader -> thread java.lang.Thread (named 'null')

其中leaked ==> 后的MemoryLeakActivity就是泄漏的Activity,它的内存地址为0x12d552d0, 持有的内存大小为 2.06MB. 后续跟着的就是这个Activity实例的引用链, 通过分析这个引用链就能够找到造成内存泄漏的关键点。整个引用链的输出顺序是按照MAT的格式输出, 如果我们将本次得到的内存快照经过hprof-conv之后导入MAT (也就是Memory Analyzer Tool,我们常用来分析内存泄漏的工具, MMAT就是基于MAT命名的.), 找到地址为0x12d552d0的MemoryLeakActivity,得到的引用链如下图所示:

可以看到,我们通过MMAT输出的引用链与通过MAT得到的基本一致。

再回过头来分析上图中的第一条内存泄漏记录, 对于我们来说, 造成内存泄漏的关键点是static com.example.mmat.MemoryLeakActivity.sActivityLeaked (这个需要根据自己的代码以及报告中的信息具体分析), 表示的是MemoryLeakActivity类中的静态字段 sActivityLeaked 引用了这个MemoryLeakActivity实例. 从后续的引用链信息看, 这个sActivityLeaked应该是一个LinkedList类型, MemoryLeakActivity是它的其中一个元素.

我们再到MemoryLeakActivity中查看 sActivityLeaked 相关的代码, 如下所示:

/**
 * 产生内存泄漏的页面
 */
public class MemoryLeakActivity extends AppCompatActivity {

    private static List<Activity> sActivityLeaked = new LinkedList<>() ;
    
    // ... 其他代码
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        
		 // memory leaked
        sActivityLeaked.add(this) ;
    }
}

可以看到, 我们在 onCreate 函数中将MemoryLeakActivity添加到 sActivityLeaked中, 但是没有在任何地方删除, 因此造成了内存泄漏, 去掉相关的代码即可解决这个内存泄漏.

其他的内存泄漏也是通过类似的方法根据报告进行分析即可.

其他重要配置

如果你不想通过Monkey操作App, 也可以自己操作App进入、退出各个Activity, 然后将App退到后台, 再将 mmat 配置文件中的monkey_command置空, 然后再执行 ./gradlew startMmatRunner, 此时就会直接dump app的内存快照, 然后进行后续的拉取内存快照以及分析内存的工作.

而如果你已经有了一份App的内存快照hprof文件, 你可以在mmat配置中执行 hprof的路径 (即 hprofFile 参数),然后执行 ./gradlew startMmatRunner任务, MMAT将直接分析这个 hprof文件, 最终分析出该内存快照中的内存泄漏.

三、mmat-config.json 配置说明

  • package: 要测试的应用包名, 必填
  • main_activity: 应用的主页面的类路径,必填 (需要在AndroidManifest.xml 注册时添加exported=true)
  • monkey_command: monkey 命令(字符串命令)或者shell脚本路径(完整的文件路径)
  • enable_force_gc: dump hprof 文件之前是否对目标App force gc (默认为true)
  • hprof_dir: dump出来的hprof存放在Android设备中的根目录(默认为/sdcard/), Android 系统默认不允许访问/sdcard/时可以配置这个参数修改hprof存储路径, 否则无法adb pull hprof文件.
  • detect_leak_classes: 要检测内存泄漏的类列表, 主要是Activity、Fragment的子类. 如果你没有其他的类型需要检测,则不需要修改这个配置;
  • excluded_refs : 要排除的产生泄漏的对象 (例如Android系统自己的内存泄漏),只有弱引用和软引用的对象通常也要排除
    • class : 要排除的类名
    • fields : 字段列表, 即排除某个类的某个字段产生的内存泄漏
    • type: 代表是静态字段(static) 还是实例字段(instance)产生的内存泄漏
  • bitmap_report : bitmap报告配置
    • max_report_count: 报告中最多输出多少张图片, 设置为-1时不做数量限制
    • min_width : 报告中如果图片的宽度小于这个值则不输出
    • min_height: 报告中如果图片的高度小于这个值则不输出

mmat-config.json 示例如下:

{
	"package": "com.example.mmat",
	"main_activity": "com.example.mmat.MainActivity",
	"monkey_command": "adb shell monkey -p com.example.mmat --ignore-crashes --ignore-timeouts --ignore-native-crashes --ignore-security-exceptions --pct-touch 40 --pct-motion 25 --pct-appswitch 10 --pct-rotation 5 -s 12358 -v -v -v --throttle 300 200",
	// "monkey_command": "/User/mrsimple/test_monkey.sh",   // monkey脚本配置
	"enable_force_gc": true,
	"hprof_dir": "/data/local/tmp/",
	"detect_leak_classes": [
		"android.app.Activity", 
		"android.app.Fragment", 
		"android.support.v4.app.Fragment"
	],
	"excluded_refs": [
		{
			"class": "java.lang.ref.WeakReference",
			"fields": ["referent"],
			"type": "instance"
		},
		{
			"class": "java.lang.ref.SoftReference",
			"fields": ["referent"],
			"type": "instance"
		},
		{
			"class": "java.lang.ref.FinalizerReference",
			"fields": ["referent"],
			"type": "instance"
		},
		{
			"class": "android.arch.lifecycle.ReportFragment",
			"fields": [],
			"type": "static"
		}
	],
	"bitmap_report": {
		"max_report_count": 20,
		"min_width": 200,
		"min_height": 200
	}
}

上述示例中Monkey的事件数量只有200次, 真实的测试场景可以设置为更高的事件数, 例如我们的Jenkins平台中Monkey测试事件数会设置为几万次. 具体事件数量开发者可以根据情况进行设定, 但不应该设置为太低的事件数, 否则内存泄漏可能不容易出现.

四、AndroidManifest.xml application 配置样例

注意, 只在【测试时】添加这些配置即可 (可以通过 flavor 对不同版本进行不同的配置).

  • application中配置 android:debuggable="true", 这样确保在debug、release模式下可以通过 adb shell dump 内存数据 (hprof)
  • MainActivity (App主页面) 中配置 android:exported="true"android:launchMode="singleTask"
    • android:exported="true": 保证可以通过 adb shell 启动主页面
    • android:launchMode="singleTask" : 保证通过adb shell 启动主页面时, Activity栈中的其他页面会被清除. 此时只有主页面在栈顶, 即除了主页面之外的其他Activity都应该已经销毁. 这样通过Monkey测试完之后, 再通过 adb shell 启动主页面后就只有主页面存在, 然后再通过adb shell将app退到后台, 此时主页面也会被销毁, 然后再dump 内存快照, 再分析内存, 此时如果还存在Activity、Fragment则表示产生了内存泄漏.

示例如下:

    <application
        android:debuggable="true"
            
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

License

Copyright (C) 2019 Mr.Simple <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

awesome-english-ebooks

经济学人(含音频)、纽约客、卫报、连线、大西洋月刊等英语杂志免费下载,支持epub、mobi、pdf格式, 每周更新
CSS
20,476
star
2

android-tech-frontier

【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目
10,443
star
3

the-economist-ebooks

经济学人(含音频)、纽约客、自然、新科学人、卫报、科学美国人、连线、大西洋月刊、国家地理等英语杂志免费下载、订阅(kindle推送),支持epub、mobi、pdf格式, 每周更新. The Economist 、The New Yorker 、Nature、The Atlantic 、New Scientist、The Guardian、Scientific American、Wired magazines, free download and subscription for kindle, mobi、epub、pdf format.
CSS
8,890
star
4

AndroidEventBus

A lightweight eventbus library for android, simplifies communication between Activities, Fragments, Threads, Services, etc.
Java
1,611
star
5

Colorful

基于Theme的Android动态换肤库,无需重启Activity、无需自定义View,方便的实现日间、夜间模式。
Java
1,398
star
6

iOS-tech-frontier

一个定期翻译国外iOS优质的技术、开源库、软件架构设计、测试等文章的开源项目
1,202
star
7

mockito-doc-zh

Mockito框架中文文档
1,113
star
8

android_my_pull_refresh_view

android下拉刷新实现原理【阐述基本原理,不建议使用】
Java
275
star
9

simple_net_framework

SimpleNet网络框架,仅供学习参考,不推荐使用,不进行维护。
Java
139
star
10

android_dp_analysis_code

《Android 源码设计模式解析与实战》示例代码
Java
130
star
11

the-tech-frontier-app

The Android App for Tech Frontier
Java
121
star
12

simple_imageloader

A Simple ImageLoader for Android 【不建议使用到项目中,仅作示例】
Java
120
star
13

commonadapter

通用的ListView、RecyclerView的Adapter
Java
107
star
14

app-test-arch

Android 单元测试、Monkey、LeakCanary测试demo项目【粗略示例】
Java
98
star
15

android_jtm_sourcecode

Java
85
star
16

SimpleTvIME

简单的Android Tv远程遥控器 & 输入法 【不进行维护】
Java
79
star
17

dp-issues

Android源码设计模式解析与实战勘误
70
star
18

Chris-Android-PullToRefresh

在Chris Banes的Android-PullToRefresh的基础上增加了RecyclerView的支持与Demo
Java
56
star
19

ripplelayout

RippleLayout library
Java
50
star
20

the-hot-tech-blogs

每周六会从掘金抓取7天内最热门Android、iOS、后端、前端各20篇技术文章, 然后制作成 mobi、epub 格式的电子书推送给订阅者, 支持推送到kindle 设备、kindle手机app、普通邮箱.
42
star
21

SegmentView

This is a Android Segment Control ( View ) like iOS, support 2.3 and above.
Java
39
star
22

materiallayout

materiallayout library and demo.
Java
34
star
23

listview_nested_gridview

ListView嵌套GridView的消息流页面,解决了GridView高度和宽度问题
Java
32
star
24

jtm-techfrontier-app

Java
31
star
25

CrashCanary

Crash Log Viewer
Java
30
star
26

leakcanary-for-eclipse

用于eclipse的leakcanary
Java
30
star
27

AndroidShakeLib

Android 摇一摇手机功能
Java
23
star
28

android-jtm-issues

《Android开发进阶-从小工到专家》勘误
23
star
29

InjectDagger

Java
22
star
30

ErasableTextView

仿支付宝刮奖效果,自定义view
Java
20
star
31

umeng-social-custom-shareui

custom ui
Java
19
star
32

AndroidMvpBase

Android MVP Base Library
Java
18
star
33

simpledb

A Lightweight android database library
Java
12
star
34

AndroidImageBrowser

Android Image Browser
Java
12
star
35

tech-frontier-mvp

8
star
36

common_adapter_viewholder

common_adapter_viewholder
Java
7
star
37

FastScrollLayout

Android fast scroll layout from ListView
Java
6
star
38

te_audios

The Economist audio
5
star
39

Java-23-Design-Patterns

23 design patterns
Java
5
star
40

MockServer

mock server with Go
Go
4
star
41

mdict-analysis

Python
4
star
42

SimpleTask

A Simple Task Library to use AsyncTask easier.
Java
4
star
43

en_dict

3
star
44

bboyfeiyu.github.io

Blog repository
HTML
3
star
45

hehonghui.github.io

HTML
3
star
46

images_gridview_gallery

扫描本地图片,多图选择,异步加载,图片错位示例
Java
3
star
47

LoveWeRead

Java
3
star
48

zapr-sdk-sample-app

Java
2
star
49

SimpleNote

Java
2
star
50

fribidi

C
1
star
51

MusicPlayer2

MusicPlayer
C++
1
star
52

VolleyModule

使用Volley时封装的简单工具类
Java
1
star
53

GradleDemo

Java
1
star
54

dev-tech-frontier

开发技术前线
1
star
55

AndroidEventBus_maven

1
star