简介

本文介绍如何在 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 里面也得到应用。简化后的示例代码如下:

 import android.database.Cursor;
import android.database.CursorWrapper;
import android.util.Log; public class TestCursor extends CursorWrapper {
private static final String TAG = "TestCursor";
private boolean mIsClosed = false;
private Throwable mTrace; public TestCursor(Cursor c) {
super(c);
mTrace = new Throwable("Explicit termination method 'close()' not called");
} @Override
public void close() {
mIsClosed = true;
} @Override
public void finalize() throws Throwable {
try {
if (mIsClosed != true) {
Log.e(TAG, "Cursor leaks", mTrace);
}
} finally {
super.finalize();
}
}
}

然后查询的时候,把 TestCursor 作为查询结果返回给 APP:

 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 并使程序强行退出。

 import android.os.StrictMode;

 public class TestActivity extends Activity {
private static final boolean DEVELOPER_MODE = true;
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setVMPolicy(new StrictMode.VMPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
}

对于 framework 开发人员

如果是通过 ContentProvider 提供数据库数据,在 ContentResolver 里面已有 CloseGuard 类实行类似检测,但需要自行打开(上例也是打开 CloseGuard):

 CloseGuard.setEnabled(true);

更值得推荐的办法是按照本文第一节中的检测原理,在 ContentResolver 内部类 CursorWrapperInner 里面加入。其他需要检测类似于资源泄漏的,同样可以使用该检测原理。

3. 容易出错的地方

忘记调用 close() 这种低级错误没什么好说的,这种应该也占不小的比例。下面说说不太明显的例子。

提前返回

有时候粗心会犯这种错误,在 close() 调用之前就 return 了,特别是函数比较大逻辑比较复杂时更容易犯错。这种情况可以通过把 close() 放在 finally 代码块解决

 private void method() {
Cursor cursor = query(); // 假设 query() 是一个查询数据库返回 Cursor 结果的函数
if (flag == false) { // !!提前返回
return;
}
cursor.close();
}

类的成员变量

假设类里面有一个在类全局有效的成员变量,在方法 A 获取了查询结果,后面在其他地方又获取了一次查询结果,那么第二次查询的时候就应该先把前面一个 Cursor 对象关闭。

 public class TestCursor {
private Cursor mCursor; private void methodA() {
mCursor = query();
} private void methodB() {
// !!必须先关闭上一个 cursor 对象
mCursor = query();
}
}

注意:曾经遇到过有人对 mCursor 感到疑惑,明明是同一个变量为什么还需要先关闭?首先 mCursor 是一个 Cursor 对象的引用,在 methodA 时 mCursor 指向了 query() 返回的一个 Cursor 对象 1;在 methodB() 时它又指向了返回的另外一个 Cursor 对象 2。在指向 Cursor 对象 2 之前必须先关闭 Cursor 对象 1,否则就出现了 Cursor 对象 1 在 finalize() 之前没有调用 close() 的情况。

异常处理

打开和关闭 Cursor 之间的代码出现 exception,导致没有跑到关闭的地方:

 try {
Cursor cursor = query();
// 中间省略某些出现异常的代码
cursor.close();
} catch (Exception e) {
// !!出现异常没跑到 cursor.close()
}

这种情况应该把 close() 放到 finally 代码块里面:

 Cursor cursor = null;
try {
cursor = query();
// 中间省略某些出现异常的代码
} catch (Exception e) {
// 出现异常
} finally {
if (cursor != null)
cursor.close();
}

4. 总结思考

在 finalize() 里面检测是可行的,且基本可以满足需要。针对 finalize() 执行时间不确定以及可能不执行的问题,可以通过记录目前打开没关闭的 Cursor 数量来部分解决,超过一定数目发出警告,两种手段相结合。

还有没有其他检测办法呢?有,在 Cursor 构造方法以及 close() 方法添加 log,运行一段时间后检查 log 看哪个地方没有关闭。简化代码如下:

 import android.database.Cursor;
import android.database.CursorWrapper;
import android.util.Log; public class TestCursor extends CursorWrapper {
private static final String TAG = "TestCursor";
private Throwable mTrace; public TestCursor(Cursor c) {
super(c);
mTrace = new Throwable("cusor opened here");
Log.d(TAG, "Cursor " + this.hashCode() + " opened, stacktrace is: ", mTrace);
} @Override
public void close() {
mIsClosed = true;
Log.d(TAG, "Cursor " + this.hashCode() + " closed.");
}
}

检查时看某个 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

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

  1. 如何检测 Android Cursor 泄漏

    简介: 本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例.有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常.同时该方法同样适合于其他需要 ...

  2. 如何在linux下检测内存泄漏

    之前的文章应用 Valgrind 发现 Linux 程序的内存问题中介绍了利用Linux系统工具valgrind检测内存泄露的简单用法,本文实现了一个检测内存泄露的工具,包括了原理说明以及实现细节. ...

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

    本文转自:http://www.ibm.com/developerworks/cn/linux/l-mleak/ 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨.其中包括 ...

  4. [原理] Android Native内存泄漏检测原理解析

    转载请注明出处:https://www.cnblogs.com/zzcperf/articles/11615655.html 上一篇文章列举了不同版本Android OS内存泄漏的检测操作(传送门), ...

  5. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  6. 利用Android Studio、MAT对Android进行内存泄漏检测

    利用Android Studio.MAT对Android进行内存泄漏检测 Android开发中难免会遇到各种内存泄漏,如果不及时发现处理,会导致出现内存越用越大,可能会因为内存泄漏导致出现各种奇怪的c ...

  7. [教程] Android Native内存泄漏检测方法

    转载请注明出处:https://www.cnblogs.com/zzcperf/p/9563389.html Android 检测 C/C++内存泄漏的方法越来越简便了,下面列举一下不同场景下检测C/ ...

  8. [轉]Android的内存泄漏和调试

    一. Android的内存机制 Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似.程序员通过new为对象分配内存,所有对象在java堆内分配空间:然而对象的 ...

  9. android 常见内存泄漏原因及解决办法

    android常见内存泄漏主要有以下几类: 一.Handler 引起的内存泄漏. 在Android开发中,我们经常会使用Handler来控制主线程UI程序的界面变化,使用非常简单方便,但是稍不注意,很 ...

随机推荐

  1. php导出excel(xls或xlsx)

    $titles = array('订单号','商品结算码','合同号','供应商名称','专柜','商品名称','商品货号','商品单价','商品总价','供应商结算金额','商品数量','商品促销优 ...

  2. js动态创建表格,删除行列的小例子

    js动态创建表格,删除行列的实例代码. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...

  3. 在Unity3D中连接WCF服务端

    服务端不多讲解,有一处需要改的地方.具体服务端请看WCF入门学习2-控制台做为宿主 建议实际项目不要拿去用,毕竟是mono不是原生.net.或许是个坑 由于Unity的mono版本问题不能直接用net ...

  4. sudo日志记录记录(rsyslog)

    1,查软件 rpm -qa|egrep "sudo|rsyslog" 2,编辑sudoers echo "Defaults logfile=/var/log/sudo.l ...

  5. [svc]Iaas Paas Saas区别

    https://www.zhihu.com/question/20387284

  6. 【Android】3.14 公交线路查询功能

    分类:C#.Android.VS2015.百度地图应用: 创建日期:2016-02-04 一.简介 利用BusLineSearch方法可查询公交线路的详情信息. 二.运行截图 简介:介绍查询公交线路功 ...

  7. win7下安装curl

    先去官网下载curl,地址https://winampplugins.co.uk/curl/,我下载的版本是curl_7_52_1_openssl_nghttp2_x64.然后执行curl.exe并且 ...

  8. ARM中LDR伪指令与LDR加载指令

    ARM指令集中,LDR通常都是作加载指令的,但是它也可以作伪指令. LDR伪指令的形式是“LDR Rn,=expr”.下面举一个例子来说明它的用法. COUNT EQU       0x4000310 ...

  9. ny104 最大和

    最大和 时间限制:1000 ms  |  内存限制:65535 KB 难度:5 描述 给定一个由整数组成二维矩阵(r*c),现在需要找出它的一个子矩阵,使得这个子矩阵内的所有元素之和最大,并把这个子矩 ...

  10. [转]编写Android.mk中的LOCAL_SRC_FILES的终极技巧

    希望看原文的请移步:[原创]编写Android.mk中的LOCAL_SRC_FILES的终极技巧 问题的引入 在使用NDK编译C/C++项目的过程中,免不了要编写Android.mk文件,其中最重要的 ...