这篇文章主要配套与Android内存优化之——static使用篇向大家介绍MAT工具的使用,我们分析的内存泄漏程序是上一篇文章中static的使用内存泄漏的比较不容易发现泄漏的第二情况和第三种情况——不正确使用单例和asyncTask造成的内存泄漏现象,没看上一篇文章的大家可以先阅读下上一篇文章。 
先看一下我们需要分析的目标程序由3个activity组成:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Button mNextButton;
private TextView pageTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pageTextView= (TextView) findViewById(R.id.tv_page);
pageTextView.setText("MainActivity");
mNextButton= (Button) findViewById(R.id.btn_next);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,SigleLeakActivity.class);
startActivity(intent);
}
});
}
}

这是一个正常的activity主要是用来启动后面的activity页面。

SigleLeakActivity.java

public class SigleLeakActivity extends AppCompatActivity{

    private MyListener mMyListener=new MyListener() {
@Override
public void onSomeThingHappen() {
}
};
private TestManager testManager=TestManager.getInstance();
private Button mNextButton;
private TextView pageTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pageTextView= (TextView) findViewById(R.id.tv_page);
pageTextView.setText("SigleLeakActivity");
mNextButton= (Button) findViewById(R.id.btn_next);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(SigleLeakActivity.this,AysncTaskLeakActivity.class);
startActivity(intent);
}
});
testManager.registerListener(mMyListener);
} }

TestManager 单例

public class TestManager {
public static final TestManager INSTANCE = new TestManager();
private List<MyListener> mListenerList; private TestManager() {
mListenerList = new ArrayList<MyListener>();
} public static TestManager getInstance() {
return INSTANCE;
} public void registerListener(MyListener listener) {
if (!mListenerList.contains(listener)) {
mListenerList.add(listener);
}
}
public void unregisterListener(MyListener listener) {
mListenerList.remove(listener);
}
} interface MyListener {
public void onSomeThingHappen();
}

在SigleLeakActivity里,由于对单例的不正确使用会造成内存泄漏

AysncTaskLeakActivity.java

public class AysncTaskLeakActivity extends AppCompatActivity {
AsyncTask mTask;
private Button mNextButton;
private TextView pageTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pageTextView= (TextView) findViewById(R.id.tv_page);
pageTextView.setText("AysncTaskLeakActivity");
mNextButton= (Button) findViewById(R.id.btn_next);
mTask=new AsyncTask<String,Void,Void>()
{
@Override
protected Void doInBackground(String... params) {
//doSomething..
Boolean loop=true;
while (loop) {
Log.d("test","task is running");
}
return null;
}
}.execute("a task");
} }

回顾下内存泄漏的原因:

1.SigleLeakActivity 
在SigleLeakActivity中,非静态的内部类的对象都是会持有指向外部类对象的引用的,因此我们将内部类对象mMyListener让单例所持有时,由于mMyListener引用了我们的activity对象,因此造成activity对象也不能被回收了,从而出现内存泄漏现象。 
2.AysncTaskLeakActivity 
我们的内部类的实例mTask会持有对activity实例对象的引用了。查看AsyncTask的实现,会通过一个SerialExecutor串行线程池来对我们的任务进行排队,而这个SerialExecutor对象就是一个static final的常量。 
具体的引用关系是: 
1.我们的任务被封装在一个FutureTask的对象中(它充当一个runable的作用),FutureTask的实现也是通过内部类来实现的,因此它也为持有AsyncTask对象,而AsyncTask对象引用了activity对象,因此activity对象间接的被FutureTask对象给引用了。 
2.futuretask对象会被添加到一个ArrayDeque类型的任务队列的mTasks实例中 
3.mTasks任务队列又被SerialExecutor对象所持有,刚也说了这个SerialExecutor对象是一个static final的常量。 
具体AsyncTask的实现大家可以去参照下其源代码,我这里就通过文字描述一下其添加任务的实现过程就可以了,总之分析了这么多通过层层引用后我们的activity会被一个static变量所引用到。

在接下来的MAT工具的分析中我们将可以更加直观的看到造成这些内存泄漏的这层层引用关系。

最后在看下布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.thinkcool.boketest.TestActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/tv_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:text="1" />
</LinearLayout>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="跳转"/>
</FrameLayout>

布局非常简单我们放置了一个TextView用于显示当前activity的名称。一个Button用于跳转下一个activity。 
在mainActivity里跳转的是SigleLeakActivity,而在SigleLeakActivity点击button跳转到AysncTaskLeakActivity。AysncTaskLeakActivity里就没有跳转了。

下面我们进行如下操作: 
1.点击”跳转”这个button 2次,此时我们的任务栈里将会有3个Activity的实例:mainActivity、SigleLeakActivity、AysncTaskLeakActivity。 
2.然后我们按返回键2次,这样又回到了刚启动的是情况了,此时任务栈只有1个mainActivity页面。 
按照正常的情况来说,返回了的那2个activity都应该被回收掉才对,但是按照我们之前内存泄漏的分析,这2个activity由于被static变量所引用并不会被回收。

下面我们就使用Memory Analyzer工具来验证下是不是这样吧: 
使用MAT(Memory Analyzer)分析内存泄漏,当然我们得下载Memory Analyzer工具(解压即可用):http://www.eclipse.org/mat/downloads.php

首先我们在Android studio中或eclipse的DDMS里导出我们程序的hprof文件。

找到我们的目标程序点击dump hprof file按钮即可。

然后使用android sdk提供的hprof-conv工具将hprof文件转换为MAT能识别的hprof文件。 

即在android sdk platform-tools目录下执行: 
hprof-conv.exe filename.hprof filename-conv.hprof

然后打开MAT,打开filename-conv.hprof文件: 

可以看到MAT提供了很多功能,如:Histogram:可以直观的看到不同类型的buffer的数量和占用的内存大小,Dominator Tree:则把内存中对象按照从大到小进行了排序。其中我们还可以使用OQL对我们想要关心的object进行查找功能,在这个例子里我们主要分析的时候静态变量对activity对象的引用所造成的内存泄漏现象,因此我们可以进行如下操作: 

打开OQL页面,然后输入select * from instanceof android.app.Activity查询条件,然后点击红色感叹号执行。 
这里我们可以看到如我们分析的一样虽然任务栈里只有了一个activity对象,但另外那2个testActivity对象仍然没有被释放。

进一步分析:对那两个activity分别进行如下操作,右键->Path To GC Root->exclude wake/soft refrence(这里排除了弱引用和软引用,因为两者被gc回收的几率较大)。

分析SigleLeakActivity:

分析AysncTaskLeakActivity:

看到到这两张图,就显得非常明了了,对于之前分析的在SigleLeakActivity中,和AysncTaskLeakActivity中,activity和static变量的层层引用关系都显示在分析图上了。 
1.SigleLeakActivity(发现最终的引用就是INSTANCE这个静态常量) 
2.AysncTaskLeakActivity(发现最终的引用就是serial_exector这个静态常量) 
这样我们根据这个层级关系就可以定位到内存泄漏的位置就是在对testManager这个单例和对asyncTask的使用这里了。再仔细观察代码,修改后就能做到对内存使用的优化了。

修改优化后的代码:

SigleLeakActivity中:

 @Override
protected void onDestroy() {
testManager.unregisterListener(mMyListener);
super.onDestroy();
}

AysncTaskLeakActivity中:

 ....
Boolean loop=true;
while (loop) {
if(isCancelled()) {
Log.d("test","task exit");
return null;
}
Log.d("test","task is running");
}
return null;
....
@Override
protected void onDestroy() {
mTask.cancel(true);
super.onDestroy();
}

然后按照之前的步骤导出hprof文件用MAT再次分析:

此时就没有了内存泄漏的现象了。

对于MAT的使用(用于排除static对activity的引用而造成的泄漏问题)就介绍到这儿,最后将代码上传至github做个标记(https://github.com/CoolThink/BokeTest.git)。

Android内存优化之——static使用篇(使用MAT工具进行分析)的更多相关文章

  1. Android内存优化之——static使用篇

    在Android开发中,我们经常会使用到static来修饰我们的成员变量,其本意是为了让多个对象共用一份空间,节省内存,或者是使用单例模式,让该类只生产一个实例而在整个app中使用.然而在某些时候不恰 ...

  2. Android内存优化(五) Lint代码扫描工具

     1.使用 工具栏 -> Analyze -> Inspect Code… 点击 Inspect Code 后会弹出检查范围的对话框: 默认是检查整个项目,我们可以点击 Custom sc ...

  3. 【腾讯Bugly干货分享】Android内存优化总结&实践

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智 ...

  4. 大礼包!ANDROID内存优化(大汇总)

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

  5. ANDROID内存优化——大汇总(转)

    原文作者博客:转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! ANDROID内存优化(大汇总——上) 写在最前: 本文的思路主要借鉴了20 ...

  6. ANDROID内存优化(大汇总——中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  7. Android内存优化大全(中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  8. 关于Android内存优化你应该知道的一切

    介绍 在Android系统中,内存分配与释放分配在一定程度上会影响App性能的—鉴于其使用的是类似于Java的GC回收机制,因此系统会以消耗一定的效率为代价,进行垃圾回收. 在中国有句老话:”由俭入奢 ...

  9. ANDROID内存优化(大汇总——全)

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

随机推荐

  1. LeetCode(4) || Longest Palindromic Substring 与 Manacher 线性算法

    LeetCode(4) || Longest Palindromic Substring 与 Manacher 线性算法 题记 本文是LeetCode题库的第五题,没想到做这些题的速度会这么慢,工作之 ...

  2. 我的iphone6退货之路

    开篇 匆匆这一年又快结束了,眼看年关将近,老婆的生日也快到了,正打算给老婆买个礼物,由于现在老婆用的手机是公司的工程机,而且还是低端产品,所以一直想给老婆改善改善,也算是对老婆这一年来辛苦的默默的支持 ...

  3. bzoj 2152: 聪聪可可 树的点分治

    2152: 聪聪可可 Time Limit: 3 Sec  Memory Limit: 259 MBSubmit: 485  Solved: 251[Submit][Status] Descripti ...

  4. [BZOJ 3196] 213平衡树 【线段树套set + 树状数组套线段树】

    题目链接:BZOJ - 3196 题目分析 区间Kth和区间Rank用树状数组套线段树实现,区间前驱后继用线段树套set实现. 为了节省空间,需要离线,先离散化,这样需要的数组大小可以小一些,可以卡过 ...

  5. XP系统VPN设置

    为了解除公司上网策略限制,或者为了上Google,Facebook,都可以通过设置VPN实现. 要使用VPN需要到VPN服务商注册,链接VPN服务商. ======================== ...

  6. 【Java】数据库连接池技术

    JDBC的问题 在程序中,我们经常要建立与数据库的连接,之后再关闭这个连接.我们知道,数据库连接对象的创建是比较消耗系统性能的,这些频繁的操作势必会消耗大量的系统资源.因此我们需要采用更高效的数据库访 ...

  7. h.264宏块与子宏块类型

    宏块类型mb_type 宏块类型表示的是宏块不同的分割和编码方式,在h.264的语法结构中,宏块类型在宏块层(macroblock_layer)中用mb_type表示(请参考h.264语法结构分析中的 ...

  8. java基础随笔-内部类

    今天来复习下内部类的一些基础知识. 首先是内部类的分类: 1.成员内部类 2.静态内部类 3.匿名内部类 4.局部内部类 下面逐一来介绍下. 首先是成员内部类,就是将内部类作为一个成员变量来处理.具体 ...

  9. 【转】蓝牙物理链路类型:SCO和ACL链路

    原文网址:http://blog.chinaunix.net/uid-23193900-id-3272233.html  蓝牙物理链路ACL(Asynchronous Connectionless), ...

  10. HDOJ(HDU) 2524 矩形A + B(推导公式、)

    Problem Description 给你一个高为n ,宽为m列的网格,计算出这个网格中有多少个矩形,下图为高为2,宽为4的网格. Input 第一行输入一个t, 表示有t组数据,然后每行输入n,m ...