这篇文章主要配套与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. Junit 源码剖析(二)

    junit4 下的所有的testcase都是在Runner下执行的, 可以将Runner理解为junit运行的容器, 默认情况下junit会使用JUnit4ClassRunner作为所有testcas ...

  2. css详解笔记

    CSS中的块级元素与行内元素 块级元素特性: 1.占据一整行,总是重起一行并且后面的元素也必须另起一行显示. 2.内联元素特性: 3.和其他内联元素显示在同一行. 块级元素列举如下: div(文档分区 ...

  3. POJ Code the Tree 树的pufer编号

    Code the Tree Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 2259   Accepted: 859 Desc ...

  4. Codeforces Round #131 (Div. 2) : B

    首先能被2,5整除的数结尾必须是0: 如果没有0肯定不行: 然后判断他们的和ans%3: 如果==0,直接从大到小输出就行: 如果==1,要么删除它们之间最小的那个%3==1的,要么删除两个小的并且% ...

  5. lua中求table长度

    关于lua table介绍,看以前的文章http://www.cnblogs.com/youxin/p/3672467.html. 官方文档是这么描述#的: 取长度操作符写作一元操作 #. 字符串的长 ...

  6. ORACLE数据缓冲区DB cache

    DB CACHE是以数据块为单位组织的缓冲区,数据库刚刚启动的时候,DB CACHE中几乎没有用户数据的缓冲,当会话访问数据库中的表或索引时,首先会检查DB CACHE中是否存在该数据,如果不存在,就 ...

  7. 【HDOJ】1754 I Hate It

    线段树. #include <iostream> #include <cstdio> #include <cstring> using namespace std; ...

  8. bzoj2120 2453

    明显的数据结构题这道题的特殊性在于n只有10000,修改的操作只有1000那么就是说即便是O(n)的修改也没有太大的问题,只要常数写小一点即可考虑到以前对同色点的处理pre[i]表示与这个位置同色的前 ...

  9. vijosP1902学姐的清晨问候

    题目:https://vijos.org/p/1902 题解:sb题...扫一遍每个字母出现的次数即可 代码: #include<cstdio> #include<cstdlib&g ...

  10. Python IDLE 清屏工具

     转载自:http://www.cnblogs.com/maybego/p/3234055.html   1.下载clearwindow.py(右击-目标另存为,直接点击会打开脚本内容). 2.拷贝c ...