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.)
3634 3644 E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid 1565=866)
3634 3644 E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)
3634 3644 E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
3634 3644 E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
3634 3644 E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
3634 3644 E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)
3634 3644 E JavaBinder: at dalvik.system.NativeStart.run(Native Method)

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 代码块里面:


 1 Cursor cursor = null;
2 try {
3 cursor = query();
4 // 中间省略某些出现异常的代码
5 } catch (Exception e) {
6 // 出现异常
7 } finally {
8 if (cursor != null)
9 cursor.close();
10 }

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-cn.html

时间: 2024-10-10 06:59:42

Android检测Cursor泄漏的原理以及使用方法(转)的相关文章

如何检测 Android Cursor 泄漏

简介: 本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例.有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常.同时该方法同样适合于其他需要检测资源泄露的情况. 最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用.另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bu

如何在linux下检测内存泄漏(转)

本文转自:http://www.ibm.com/developerworks/cn/linux/l-mleak/ 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨.其中包括 C++ 中的 new 和 delete 的基本原理,内存检测子系统的实现原理和具体方法,以及内存泄漏检测的高级话题.作为内存检测子系统实现的一部分,提供了一个具有更好的使用特性的互斥体(Mutex)类. 1.开发背景 在 windows 下使用 VC 编程时,我们通常需要 DEBUG 模式下运行程

Android Native内存泄漏检测方法

Android 检测 C/C++内存泄漏的方法越来越简便了,下面列举一下不同场景下检测C/C++内存泄漏的方法. Android O(针对root设备,调试APP) 1. 准备一个userdebug或eng版本手机,下载native_heapdump_viewer.py脚本备用 2. 执行以下命令 adb shell setprop wrap.<APP_PACKAGE_NAME> '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace"' 3. 执行重现

Android实战——LeakCanary检测内存泄漏

本篇文章包括以下内容: 前言 内存泄漏的简介 内存溢出的简介 LeakCanary的配置与使用 结语 内存泄漏对于初学者们可能是一个陌生的词语,但是却频频发生于自己的软件上,只不过自己不知道而已.同理,内存溢出也是一个道理.而内存泄漏和内存溢出常常是面试的考题,所以早点掌握是必不可少的 内存泄漏是指:对象在它有限的生命周期结束时,它们将被垃圾回收,如果在回收时,这个对象还被一系列的引用,导致该对象不会被回收,那么就会导致内存泄漏.随着泄漏的累积,应用将消耗完内存,应用的流畅性就会大大减弱 常见的

Android -- 检测耳机插入状态

原理                                                                                    其实android系统在耳机插入和拔出的时候都会发送广播,所以我们要想检测耳机的状态只需要注册响应的BroadCastReceiver,对状态进行响应的判断就ok了. 这个广播的名字叫做:android.intent.action.HEADSET_PLUG Code                               

Android反射机制实现与原理

本文介绍Android反射机制实现与原理,在介绍之前,要和Java进行比较,所以先看下Java中的反射相关知识: 一.反射的概念及在Java中的类反射 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力.在计算机科学领域,反射是一类应用,它们能够自描述和自控制.这类应用通过某种机制来实现对自己行为的描述和检测,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 在Java中的反射机制,被称为Reflection(大家看到这个单词,第一个想法应该就是去开发文档中

Android native CursorWindow数据保存原理

我们通过Uri查询数据库所得到的数据集,保存在native层的CursorWindow中.CursorWindow的实质是共享内存的抽象,以实现跨进程数据共享.共享内存所采用的实现方式是文件映射. 在ContentProvider端透过SQLiteDatabase的封装查询到的数据集保存在CursorWindow所指向的共享内存中,然后通过Binder把这片共享内存传递到ContentResolver端,即查询端.这样客户就可以通过Cursor来访问这块共享内存中的数据集了. 那么CursorW

Android防止内存泄漏以及MAT的使用

Android发生内存泄漏最普遍的一种情况就是长期保持对Context,特别是Activity的引用,使得Activity无法被销毁.这也就意味着Activity中所有的成员变量也没办法销毁.本文仅介绍如何避免这种情况的发生,其他如Bitmap没有及时回收导致的OOM异常暂不讨论. 一.防止内存泄漏 什么情况下会长时间保持对某个Activity的引用呢?主要有以下两种情况: 1.某个static变量保持对Activity的引用 2.线程保持Activity的引用 由于静态变量是长驻内存的,甚至在

Android sqlite cursor的遍历

查询并获得了cursor对象后,用while(corsor.moveToNext()){}遍历,当corsor.moveToNext()方法调用,如果发现没有对象,会返回false public List<MMImage> getAll() { List<MMImage> list = new ArrayList<MMImage>(); Cursor c = null; try { c = database.query(TABLE, null, null, null,