如何检测 Android Cursor 泄漏
简介:
本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时该方法同样适合于其他需要检测资源泄露的情况。
最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用。另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bug 很长时间都没被发现。
但是一旦 Cursor 泄漏累计到一定数目(通常为数百个)必然会出现无法查询数据库的情况,只有等数据库服务所在进程死掉重启才能恢复正常。通常的出错信息如下,指出某 pid 的程序打开了 866 个 Cursor 没有关闭,导致了 exception:
3634 3644 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.) |
1. Cursor 检测原理
在 Cursor 对象被 JVM 回收运行到 finalize() 方法的时候,检测 close() 方法有没有被调用,此办法在 ContentResolver 里面也得到应用。简化后的示例代码如下:
1 import android.database.Cursor;
2 import android.database.CursorWrapper;
3 import android.util.Log;
4
5 public class TestCursor extends CursorWrapper {
6 private static final String TAG = "TestCursor";
7 private boolean mIsClosed = false;
8 private Throwable mTrace;
9
10 public TestCursor(Cursor c) {
11 super(c);
12 mTrace = new Throwable("Explicit termination method 'close()' not called");
13 }
14
15 @Override
16 public void close() {
17 mIsClosed = true;
18 }
19
20 @Override
21 public void finalize() throws Throwable {
22 try {
23 if (mIsClosed != true) {
24 Log.e(TAG, "Cursor leaks", mTrace);
25 }
26 } finally {
27 super.finalize();
28 }
29 }
30 }
然后查询的时候,把 TestCursor 作为查询结果返回给 APP:
1 return new TestCursor(cursor); // cursor 是普通查询得到的结果,例如从 ContentProvider.query()
该方法同样适合于所有需要检测显式释放资源方法没有被调用的情形,是一种通用方法。但在 finalize() 方法里检测需要注意
优点:准确。因为该资源在 Cursor 对象被回收时仍没被释放,肯定是发生了资源泄露。
缺点:依赖于 finalize() 方法,也就依赖于 JVM 的垃圾回收策略。例如某 APP 现在有 10 个 Cursor 对象泄露,并且这 10 个对象已经不再被任何引用指向处于可回收状态,但是 JVM 可能并不会马上回收(时间不可预测),如果你现在检查不能够发现问题。另外,在某些情况下就算对象被回收 finalize() 可能也不会执行,也就是不能保证检测出所有问题。关于 finalize() 更多信息可以参考《Effective Java 2nd Edition》的 Item 7: Avoid Finalizers
2. 使用方法
对于 APP 开发人员
从 GINGERBREAD 开始 Android 就提供了 StrictMode 工具协助开发人员检查是否不小心地做了一些不该有的操作。使用方法是在 Activity 里面设置 StrictMode,下面的例子是打开了检查泄漏的 SQLite 对象以及 Closeable 对象(普通 Cursor/FileInputStream 等)的功能,发现有违规情况则记录 log 并使程序强行退出。
1 import android.os.StrictMode;
2
3 public class TestActivity extends Activity {
4 private static final boolean DEVELOPER_MODE = true;
5 public void onCreate() {
6 if (DEVELOPER_MODE) {
7 StrictMode.setVMPolicy(new StrictMode.VMPolicy.Builder()
8 .detectLeakedSqlLiteObjects()
9 .detectLeakedClosableObjects()
10 .penaltyLog()
11 .penaltyDeath()
12 .build());
13 }
14 super.onCreate();
15 }
16 }
对于 framework 开发人员
如果是通过 ContentProvider 提供数据库数据,在 ContentResolver 里面已有 CloseGuard 类实行类似检测,但需要自行打开(上例也是打开 CloseGuard):
1 CloseGuard.setEnabled(true);
更值得推荐的办法是按照本文第一节中的检测原理,在 ContentResolver 内部类 CursorWrapperInner 里面加入。其他需要检测类似于资源泄漏的,同样可以使用该检测原理。
3. 容易出错的地方
忘记调用 close() 这种低级错误没什么好说的,这种应该也占不小的比例。下面说说不太明显的例子。
提前返回
有时候粗心会犯这种错误,在 close() 调用之前就 return 了,特别是函数比较大逻辑比较复杂时更容易犯错。这种情况可以通过把 close() 放在 finally 代码块解决
1 private void method() {
2 Cursor cursor = query(); // 假设 query() 是一个查询数据库返回 Cursor 结果的函数
3 if (flag == false) { // !!提前返回
4 return;
5 }
6 cursor.close();
7 }
类的成员变量
假设类里面有一个在类全局有效的成员变量,在方法 A 获取了查询结果,后面在其他地方又获取了一次查询结果,那么第二次查询的时候就应该先把前面一个 Cursor 对象关闭。
1 public class TestCursor {
2 private Cursor mCursor;
3
4 private void methodA() {
5 mCursor = query();
6 }
7
8 private void methodB() {
9 // !!必须先关闭上一个 cursor 对象
10 mCursor = query();
11 }
12 }
注意:曾经遇到过有人对 mCursor 感到疑惑,明明是同一个变量为什么还需要先关闭?首先 mCursor 是一个 Cursor 对象的引用,在 methodA 时 mCursor 指向了 query() 返回的一个 Cursor 对象 1;在 methodB() 时它又指向了返回的另外一个 Cursor 对象 2。在指向 Cursor 对象 2 之前必须先关闭 Cursor 对象 1,否则就出现了 Cursor 对象 1 在 finalize() 之前没有调用 close() 的情况。
异常处理
打开和关闭 Cursor 之间的代码出现 exception,导致没有跑到关闭的地方:
1 try {
2 Cursor cursor = query();
3 // 中间省略某些出现异常的代码
4 cursor.close();
5 } catch (Exception e) {
6 // !!出现异常没跑到 cursor.close()
7 }
这种情况应该把 close() 放到 finally 代码块里面:
4. 总结思考
在 finalize() 里面检测是可行的,且基本可以满足需要。针对 finalize() 执行时间不确定以及可能不执行的问题,可以通过记录目前打开没关闭的 Cursor 数量来部分解决,超过一定数目发出警告,两种手段相结合。
还有没有其他检测办法呢?有,在 Cursor 构造方法以及 close() 方法添加 log,运行一段时间后检查 log 看哪个地方没有关闭。简化代码如下:
1 import android.database.Cursor;
2 import android.database.CursorWrapper;
3 import android.util.Log;
4
5 public class TestCursor extends CursorWrapper {
6 private static final String TAG = "TestCursor";
7 private Throwable mTrace;
8
9 public TestCursor(Cursor c) {
10 super(c);
11 mTrace = new Throwable("cusor opened here");
12 Log.d(TAG, "Cursor " + this.hashCode() + " opened, stacktrace is: ", mTrace);
13 }
14
15 @Override
16 public void close() {
17 mIsClosed = true;
18 Log.d(TAG, "Cursor " + this.hashCode() + " closed.");
19 }
20 }
检查时看某个 hashCode() 的 Cursor 有没有调用过 close() 方法,没有的话说明资源有泄露。这种方法优点是同样准确,且更可靠。缺点是需要检查大量 log,且打开/关闭的地方可能相距较远,如果不写个小脚本分析人工看的话会比较痛苦;另外必须 APP 完全退出后才能检查,因为后台运行时某些 Cursor 还在正常使用。
转载请注明出处:http://www.cnblogs.com/imouto/archive/2013/01/14/how-to-detect-leaked-cursor.html
本文外部镜像:http://oteku.blogspot.com/2013/01/how-to-detect-android-cursor-leak-c
总结:频繁使用数据库,一定要注意cursor的问题,随用随关,不要出现这个cursor仍然未被系统关闭,又开了一个cursor的可能性,另外注意cursor的判空。一般避免频繁读取数据库的操作,有时这个bug会在一定使用次数下隐藏下来,,不易发现,当操作达到一定次数时,才会浮现。所以最好可以设置一些需要操作数据库的标志位,例如只有当数据库发生更改的标志位,此时在读取数据库之类的。
如何检测 Android Cursor 泄漏的更多相关文章
- 使用android studio检测app内存泄漏【转载】
Android开发中难免会遇到各种内存泄漏,如果不及时发现处理,会导致出现内存越用越大,可能会因为内存泄漏导致出现各种奇怪的crash,甚至可能出现因内存不足而导致APP崩溃. 一般检测android ...
- Android检测Cursor泄漏的原理以及使用方法(转)
简介: 本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例.有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常.同时该方法同样适合于其他需要 ...
- android 内存泄漏检测工具 LeakCanary 泄漏金丝雀
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 内存泄漏检测工具 android 内存泄漏检测工具 ======== 内存泄漏 就是 无用的对 ...
- Android内存泄漏检测利器:LeakCanary
Android内存泄漏检测利器:LeakCanary MAR 28TH, 2016 是什么? 一言以蔽之:LeakCanary是一个傻瓜化并且可视化的内存泄露分析工具 为什么需要LeakCanary? ...
- Android内存泄漏的检测流程、捕捉以及分析
https://blog.csdn.net/qq_20280683/article/details/77964208 Android内存泄漏的检测流程.捕捉以及分析 简述: 一个APP的性能,重度关乎 ...
- 【Android内存泄漏检测】LeakCanary使用总结
一.什么是LeakCanary? LeakCanary就是用来检测Android端内存泄漏的一个工具.能够检测Activity的泄漏 什么是内存泄漏? Java 对象有时也会”长死不死“,GC 拿它没 ...
- Android内存泄漏
Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的 ...
- Android 内存泄漏总结
内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收 ...
- Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
随机推荐
- 二分图最大匹配 Hopcroft-Karp算法模板
#include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> ...
- 为什么我的Android SDK Manager中只显示已安装的package?
如图.在菜单packages中,前两项我都是选了的 . 两种可能: 1. 明显是楼主连不上谷歌的更新服务器了,换成国内的吧,左上Tools-->Options弹出的界面有两个可以填写的 ,下面一 ...
- JSP标准标签库(JSTL)--XML标签库 x
³在开发中XML解析的操作是非常烦琐的,幸运的是在JSTL中专门提供了用于XML解析的操作,这样用户就可以不用费力的去研究SAX或DOM等操作的使用,就可以轻松的进行XML文件的解析处理. XML标 ...
- 【贪心】时空定位II
[贪心]时空定位II 题目描述 有一块空间,横向长w,纵向长为h,在它的横向中心线上不同位置处装有n(n≤10000)个点状的定位装置,每个定位装置i定位的效果是让以它为中心半径为Ri的圆都被覆盖.请 ...
- Android------自定义ListView详解
引自:http://www.cnblogs.com/phonegap/archive/2011/04/28/2535732.html 在这里我就对自定义ListView进行讲解下吧,直入真题吧,首先我 ...
- [转]使用openssl库实现RSA、AES数据加密
openssl是可以很方便加密解密的库,可以使用它来对需要在网络中传输的数据加密.可以使用非对称加密:公钥加密,私钥解密.openssl提供了对RSA的支持,但RSA存在计算效率低的问题,所以一般的做 ...
- POJ 3254 Corn Fields(状态压缩)
一道状态压缩的题,错了好多次....应该先把满足的情况预处理出来 #include<iostream> #include<cstdio> #include<cstring ...
- 配置glance使用NFS后端
首先先使用“glance image-delete”命令删除所有镜像,释放磁盘空间. 停止glance服务:service openstack-glance-api stopservice opens ...
- 写一个程序,统计自己C语言共写了多少行代码,Github基本操作
前言 在上一篇博客中,本人提到了自己的文件操作可以说是几乎没用过.现在想想,这也算是只在OJ上做题的一个弊端吧.虽然通过OJ做题是一个学习代码好手段,但其他方面也要多多涉猎才好,而不是说OJ用不到文件 ...
- JAVA基础--异常
异常的分类: 1. Throwable: 根类 1) Error:系统错误, 由java虚拟机生成并抛出, 无法处理 2) Exception: 所有异常类的父类, 可以处理的错误, 可以catch到 ...