这篇文章主要配套与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. Linux脚本

    放置在/usr/local/sbin下面: 收到一个问题:"-bash XXX 没有权限":需要在此目录下执行chmod +x filename 收到一个问题:"-bas ...

  2. 深入剖析Java中的装箱和拆箱(转)

    自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱.拆箱相关的问题. 以下是本文的 ...

  3. LeetCode 面试:Add Binary

    1 题目 Given two binary strings, return their sum (also a binary string). For example,a = "11&quo ...

  4. asp.net中水印的实现代码

    水印是为了防止别盗用我们的图片. 两种方式实现水印效果 1)可以在用户上传时添加水印. a)   好处:与2种方法相比,用户每次读取此图片时,服务器直接发送给客户就行了. b)   缺点:破坏了原始图 ...

  5. Android-补间动画效果

    Android的SDK提供了三种类型的动画,分别是补间动画.逐帧动画和插值属性动画.下面先介绍第一种动画效果-补间动画. 补间动画可以应用于View,让开发者可以定义一些关于大小.位置.旋转和透明度的 ...

  6. 14.6.8 Configuring the InnoDB Master Thread IO Rate 配置InnoDB 主线程IO 速率:

    14.6.8 Configuring the InnoDB Master Thread IO Rate 配置InnoDB 主线程IO 速率: 主线程 在InnoDB 是一个线程 执行各种任务在后台. ...

  7. What does the number on the visual studio solution icon represent?

    The numbers correspond to the internal version numbers of various editions of Visual Studio http://e ...

  8. eclipse中 com.sun.image.codec.jpeg.JPEGCodec 无法编译通过问题

    在Eclipse中处理图片,需要引入两个包:import com.sun.image.codec.jpeg.JPEGCodec;import com.sun.image.codec.jpeg.JPEG ...

  9. Delphi 编写的Web Service

      一编写服务程序 第一步:File----->New----->Other------>WebServices----->Soap Server Application选择I ...

  10. ipython notebook使用教程

    在一次师兄(师兄博客地址)的例会汇报中,介绍了ipython notebook,当时觉得很酷炫,渐渐自己使用的时候才发现真的很强大.抽空整理下,找了些资料进行补充,并挨个进行了实现,留个笔记,也欢迎喜 ...