这篇文章主要配套与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. 2016022613 - redis连接命令集合

    redis连接命令 1.ping 用途:检查服务器是否正在运行 返回数据pong,表示服务器在运行. 2.quit 用途:关掉当前服务器连接 3.auth password 用途:服务器验证密码 没有 ...

  2. 学习总结之Log4NET

    通过在网上查找了一些资料,用了些时间学习了log4NET,做了一个小小的总结,说一下它的特点吧 首先呢log4NET是.Net下一个非常优秀的开源日志记录组件.它可以将日志分成不同等级,也可以按照我们 ...

  3. vs2015安装没有wim32

    刚开始在官网上下载VS2015没在意太多,选择了默认安装,结果是没有win64的,所以就不能写c代码.默认安装很多库都没有,所以要什么都得下载.转载一篇文章

  4. JDK源码阅读(一) ArrayList

    基于JDK7.0 ArrayList<E>类继承了抽象类AbstractList<E> 实现了List<E> 接口,RandomAccess接口,Cloneable ...

  5. 提升网站用户体验—WebP 图片的高效使用

    一.WebP 的由来 现代图像压缩技术对我们的生活方式影响很大.数码相机能将上千张高质量图片存储到一张内存卡里.智能手机可以与邻近设备快速分享高分辨率的图片.网站与手机等移动设备能快速展示各种富媒体. ...

  6. DJANGO中获取登陆用名及别名

    练练,标准认证的. VIEW中导入: from django.contrib.auth.models import User TEMPLATE中可引用: 列表 {{ user.username }}{ ...

  7. QT变异版本下载(SJLJ长跳转,DWARF不传递错误(32位专用),SEH(64位专用)),以及QT的实验室项目

    http://www.tver-soft.org/ http://sourceforge.net/projects/qt64ng/ ---------------------------------- ...

  8. Windows消息对Edit控件的处理

    例如对windows发消息让文本选中. SendMessage(Text1.hwnd,EM_GETSEL,0,-1 ); EC_LEFTMARGIN(&H1) EC_USEFONTINF // ...

  9. Linux自定义命令

    linux自定义命令,就是给当前命令取个别名.比如:ls 列出当前的文件,rm + 文件名 就能删除该文件,如何自定义命令,可以使用alias比如:alias gobin='cd /opt/tomca ...

  10. leetcode 字符串分割对称

    public class Solution { public List<List<String>> partition(String s) { int len=s.length ...