android 内存泄漏检测工具 LeakCanary 泄漏金丝雀
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com
内存泄漏检测工具
android 内存泄漏检测工具
========
内存泄漏 就是 无用的对象没有被回收,占用着内存,使得可用内存变小了。
如何检测内存泄漏, 可以使用 LeakCanary来检测内存泄漏。
leak 是 泄漏的意思。.
Canary 是 金丝雀 的意思。
在运行 应用的时候, 泄漏金丝雀 如果检测到内存泄漏 会显示一个通知。
========
LeakCanary捕获常见内存泄漏以及解决办法
1.)错误使用单例造成的内存泄漏
在平时开发中单例设计模式是我们经常使用的一种设计模式,而在开发中单例经常需要持有Context对象,如果持有的Context对象生命周期与单例生命周期更短时,或导致Context无法被释放回收,则有可能造成内存泄漏,错误写法如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class LoginManager { private static LoginManager mInstance; private Context mContext; private LoginManager(Context context) { this.mContext = context; } public static LoginManager getInstance(Context context) { if (mInstance == null) { synchronized (LoginManager.class) { if (mInstance == null) { mInstance = new LoginManager(context); } } } return mInstance; } public void dealData() { }} |
若我们在一个Activity中调用的,然后关闭该Activity则会出现内存泄漏。
|
1
|
LoginManager.getInstance(this).dealData(); |
LeakCanary检测结果如下:
解决 办法要保证Context和AppLication的生命周期一样,修改后代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class LoginManager { private static LoginManager mInstance; private Context mContext; private LoginManager(Context context) { this.mContext = context.getApplicationContext(); } public static LoginManager getInstance(Context context) { if (mInstance == null) { synchronized (LoginManager.class) { if (mInstance == null) { mInstance = new LoginManager(context); } } } return mInstance; } public void dealData() { }} |
2.)Handler造成的内存泄漏
早些年Handler的使用频率还是蛮高的,它是工作线程与UI线程之间通讯的桥梁,只是现在大量开源框架对其进行了封装,我们这里模拟一种常见使用方式来模拟内存泄漏情形。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler(); private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text);//模拟内存泄露 mHandler.postDelayed(new Runnable() { @Override public void run() { mTextView.setText("lcj"); } }, 3 * 60 * 1000); finish(); } @Override protected void onDestroy() { super.onDestroy(); LApplication.getRefWatcher().watch(this); }} |
上述代码通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
LeakCanary检测结果如下:
要想避免Handler引起内存泄漏问题,需要我们在Activity关闭退出的时候的移除消息队列中所有消息和所有的Runnable。上述代码只需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null);就行了。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class MainActivity1 extends AppCompatActivity { private Handler mHandler = new Handler(); private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); //模拟内存泄露 mHandler.postDelayed(new Runnable() { @Override public void run() { mTextView.setText("lcj"); } }, 3 * 60 * 1000); finish(); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); mHandler=null; LApplication.getRefWatcher().watch(this); }} |
3.)线程造成的内存泄漏
最早时期的时候处理耗时操作多数都是采用Thread+Handler的方式,后来逐步被AsyncTask取代,直到现在采用RxJava的方式来处理异步。这里以AsyncTask为例,可能大部分人都会这样处理一个耗时操作然后通知UI更新结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public class MainActivity extends AppCompatActivity { private AsyncTask<Void, Void, Integer> asyncTask; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); testAsyncTask(); finish(); } private void testAsyncTask() { asyncTask = new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { int i = 0; //模拟耗时操作 while (!isCancelled()) { i++; if (i > 1000000000) { break; } Log.e("LeakCanary", "asyncTask---->" + i); } return i; } @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mTextView.setText(String.valueOf(integer)); } }; asyncTask.execute(); } @Override protected void onDestroy() { super.onDestroy(); LApplication.getRefWatcher().watch(this); }} |
对于上面的例子来说,在处理一个比较耗时的操作时,可能还没处理结束MainActivity就执行了退出操作,但是此时AsyncTask依然持有对MainActivity的引用就会导致MainActivity无法释放回收引发内存泄漏。
LeakCanary检测结果:
如何解决这种内存泄漏呢?在使用AsyncTask时,在Activity销毁时候也应该取消相应的任务AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏的发生。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public class MainActivity3 extends AppCompatActivity { private AsyncTask<Void, Void, Integer> asyncTask; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); testAsyncTask(); finish(); } private void testAsyncTask() { asyncTask = new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { int i = 0; //模拟耗时操作 while (!isCancelled()) { i++; if (i > 1000000000) { break; } Log.e("LeakCanary", "asyncTask---->" + i); } return i; } @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mTextView.setText(String.valueOf(integer)); } }; asyncTask.execute(); } private void destroyAsyncTask() { if (asyncTask != null && !asyncTask.isCancelled()) { asyncTask.cancel(true); } asyncTask = null; } @Override protected void onDestroy() { super.onDestroy(); destroyAsyncTask(); LApplication.getRefWatcher().watch(this); }} |
4.)非静态内部类创建静态实例造成的内存泄漏
有时我们需要一个可以随着屏幕旋转的Activity,比如视频播放Activity,这时我们为了防止多次调用onCreate方法导致某些参数重新初始化,我们一般会选择创建一个内部类和一个静态实例来保存这些参数,比如以下实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public class MainActivity extends AppCompatActivity { private static Config mConfig; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //模拟内存泄露 if (mConfig == null) { mConfig = new Config(); mConfig.setSize(18); mConfig.setTitle("老九门"); } finish(); } @Override protected void onDestroy() { super.onDestroy(); LApplication.getRefWatcher().watch(this); } class Config { private int size; private String title; public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }} |
上述代码看着没有任何问题,其实内部类都会持有一个外部类引用,这里这个外部类就是MainActivity,然而内部类实例又是static静态变量其生命周期与Application生命周期一样,所以在MainActivity关闭的时候,内部类静态实例依然持有对MainActivity的引用,导致MainActivity无法被回收释放,引发内存泄漏。LeakCanary检测内存泄漏结果如下:
对于这种泄漏的解决办法就是将内部类改成静态内部类,不再持有对MainActivity的引用即可,修改后的代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public class MainActivity extends AppCompatActivity { private static Config mConfig; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //模拟内存泄露 if (mConfig == null) { mConfig = new Config(); mConfig.setSize(18); mConfig.setTitle("老九门"); } finish(); } @Override protected void onDestroy() { super.onDestroy(); LApplication.getRefWatcher().watch(this); } static class Config { private int size; private String title; public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }} |
5.)由WebView引起的内存泄漏
在目前的开发中多多少少会用到Hybrid开发方式,这样我们就会用WebView去承载Html网页,就如下面这种方式:
java代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class MainActivity5 extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); mWebView = (WebView) findViewById(R.id.web); } @Override protected void onDestroy() { super.onDestroy(); LApplication.getRefWatcher().watch(this); }} |
xml布局文件:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?xml version="1.0" encoding="utf-8"?><LinearLayout android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <WebView android:id="@+id/web" android:layout_width="wrap_content" android:layout_height="wrap_content"/></LinearLayout> |
WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。及时Activity关闭时在onDestroy中调用如下代码也是没有任何作用。
|
1
2
3
4
5
6
7
8
9
|
private void destroyWebView() { if (mWebView != null) { mLinearLayout.removeView(mWebView); mWebView.pauseTimers(); mWebView.removeAllViews(); mWebView.destroy(); mWebView = null; } } |
先看下LeakCanary检测到的结果如下:
该如何解决呢?这个查了不少资料,其中一种就是使用getApplicationgContext作为参数构建WebView,然后动态添加到一个ViewGroup中,最后退出的时候调用webView的销毁的函数,虽然也达到了防止内存溢出的效果,但是在有些网页弹出时候需要记住密码的对话框的时候,会出现Unable to add window -- token null is not for an application 的错误,所以这里采用的解决办法是通过把使用了WebView的Activity(或者Service)放在单独的进程里。然后在检测到应用占用内存过大有可能被系统干掉或者它所在的Activity(或者Service)结束后,调用android.os.Process.killProcess(android.os.Process.myPid());,主动Kill掉进程。由于系统的内存分配是以进程为准的,进程关闭后,系统会自动回收所有内存。
修改后的代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class MainActivity5 extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); mWebView = (WebView) findViewById(R.id.web); } @Override protected void onDestroy() { destroyWebView(); android.os.Process.killProcess(android.os.Process.myPid()); super.onDestroy(); LApplication.getRefWatcher().watch(this); } private void destroyWebView() { if (mWebView != null) { mWebView.pauseTimers(); mWebView.removeAllViews(); mWebView.destroy(); mWebView = null; } }} |
manifest中对应的activity配置如下:
|
1
2
3
|
<activity android:name=".MainActivity5" android:process="com.whoislcj.webview"/> |
6.)资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。例如获取媒体库图片地址代码在查询结束的时候一定要调用
Cursor 的关闭方法防止造成内存泄漏。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
String columns[] = new String[]{ MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, MediaStore.Images.Media.TITLE, MediaStore.Images.Media.DISPLAY_NAME }; Cursor cursor = this.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null); if (cursor != null) { int photoIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); //显示每张图片的地址,但是首先要判断一下,Cursor是否有值 while (cursor.moveToNext()) { String photoPath = cursor.getString(photoIndex); //这里获取到的就是图片存储的位置信息 Log.e("LeakCanary", "photoPath---->" + photoPath); } cursor.close(); } |
总结
android 内存泄漏检测工具 LeakCanary 泄漏金丝雀的更多相关文章
- Android 内存泄漏检测工具 LeakCanary(Kotlin版)的实现原理
LeakCanary 是一个简单方便的内存泄漏检测框架,做 android 的同学基本都收到过 LeakCanary 检测出来的内存泄漏.目前 LeakCanary 最新版本为 2.7 版本,并且采用 ...
- Android内存泄露---检测工具篇
内存使用是程序开发无法回避的一个问题.如果我们毫不在意肆意使用,总有一天会为此还账,且痛不欲生...所以应当防患于未然,把内存使用细化到平时的每一行代码中. 内存使用概念较大,本篇先讲对已有app如何 ...
- 安卓 内存泄漏检测工具 LeakCanary 使用
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 配置 build.gradle dependencies { debugCompile 'com ...
- Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector
那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简 ...
- C++内存泄漏检测工具
C++内存泄漏检测工具 1.VC自带的CRT:_CrtCheckMemory 调试器和 CRT 调试堆函数 1.1用法: /************************************ ...
- C/C++的内存泄漏检测工具Valgrind memcheck的使用经历
Linux下的Valgrind真是利器啊(不知道Valgrind的请自觉查看参考文献(1)(2)),帮我找出了不少C++中的内存管理错误,前一阵子还在纠结为什么VS 2013下运行良好的程序到了Lin ...
- 内存泄漏检测工具VLD在VS2010中的使用举例
Visual LeakDetector(VLD)是一款用于Visual C++的免费的内存泄露检测工具.它的特点有:(1).它是免费开源的,采用LGPL协议:(2).它可以得到内存泄露点的调用堆栈,可 ...
- vld(Visual Leak Detector) 内存泄露检测工具
初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复 杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题.内存 ...
- 【YFMemoryLeakDetector】人人都能理解的 iOS 内存泄露检测工具类
背景 即使到今天,iOS 应用的内存泄露检测,仍然是一个很重要的主题.我在一年前,项目中随手写过一个简单的工具类,当时的确解决了大问题.视图和控制器相关的内存泄露,几乎都不存在了.后来想着一直就那个工 ...
随机推荐
- 【poj1830-开关问题】高斯消元求解异或方程组
第一道高斯消元题目~ 题目:有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关 ...
- F题 hdu 1431 素数回文
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1431 素数回文 Time Limit: 2000/1000 MS (Java/Others) M ...
- 【Eclipse】Eclipse中修改项目的映射名称与端口
1.正常部署(映射的名字为项目名字,端口为8080)
- 关于tcp连接对象在多进程中的错误:pickle.PicklingError
如果需要在多进程中使用tcp连接的对象,那么不能再主进程中将这个对象创建好当做参数传给子进程,因为在创建子进程是需要序列化对象,然而socket对象是不能序列化的,会产生一个pickle.Pickli ...
- Linux信号函数
1. signal函数: #include <signal.h> void (*signal(int signo, void (*func)(int)))(int); ret-成功返回信号 ...
- Linux 入门记录:一、命令行 Bash 的基本操作
为了以后长期的线上测试和服务器的性能考量,要用 Linux 服务器了.昨晚装了个 CentOS 6.9,今天开始学学 Linux 基础,扫扫盲.ok,小本本记 ing... 一.Shell简介 She ...
- Vim中的键映射【转】
转自:http://www.cnblogs.com/softwaretesting/archive/2011/09/28/2194515.html http://www.pythonclub.org/ ...
- 2015多校第6场 HDU 5355 Cake 贪心,暴力DFS
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5355 题意:给你n个尺寸大小分别为1,2,3,…,n的蛋糕,要求你分成m份,要求每份中所有蛋糕的大小之 ...
- Kettle使用介绍——Kettle的安装与基本使用
下面的链接是原文 http://www.cnblogs.com/limengqiang/archive/2013/01/16/KettleApply1.html
- Laravel artisan commands
使用php artisan list 可以看到artisan的所有命令以及选项. 当然你也可以在此基础上扩展自己的命令. 1. key 1.1 key:generate 这是一个加密秘钥,用于保证安全 ...