如何检测 Android Cursor qt 内存泄漏检测

Android下常见的内存泄露_Linux编程_Linux公社-Linux系统门户网站
你好,游客
Android下常见的内存泄露
来源:Linux社区&
作者:DeamonTX
因为使用Java作为开发语言,很多人在使用会不注意内存的问题。于是有时遇到程序运行时不断消耗内存,最终导致OutOfMemery,程序异常退出,这就是内存泄露导致的。
我们现在就来总结一下可能导致内存泄露的情况:
查询数据库而没有关闭Cursor
在Android中,Cursor是很常用的一个对象,但在写代码是,经常会有人忘记调用close, 或者因为代码逻辑问题状况导致close未被调用。 通常,在Activity中,我们可以调用或直接使用让Activity自动管理Cursor对象。但需要注意的是,当Activity介绍后,Cursor将不再可用!若操作Cursor的代码和UI不同步(如后台线程),那没需要先判断Activity是否已经结束,或者在调用OnDestroy前,先等待后台线程结束。除此之外,以下也是比较常见的Cursor不会被关闭的情况:
&&&&Cursor&c&=&queryCursor();&&
&&&&int&a&=&c.getInt(1);&&
&&&&......&&
&&&&c.close();&&
}&catch&(Exception&e)&{&&
}&&虽然表面看起来,Cursor.close()已经被调用,但若出现异常,将会跳过close(),从而导致内存泄露。
所以,我们的代码应该以如下的方式编写:
Cursor&c&=&queryCursor();&&
try&{&&&&&&
&&&&int&a&=&c.getInt(1);&&
&&&&......&&
}&catch&(Exception&e)&{&&
}&finally&{&&
&&&&c.close();&&&
调用registerReceiver后未调用unregisterReceiver().
在调用registerReceiver后,若未调用unregisterReceiver,其所占的内存是相当大的。而我们经常可以看到类似于如下的代码:
registerReceiver(new&BroadcastReceiver()&{&&
},&filter);&...&&这是个很严重的错误,因为它会导致BroadcastReceiver不会被unregister而导致内存泄露。
未关闭InputStream/OutputStream
在使用文件或者访问网络资源时,使用了InputStream/OutputStream也会导致内存泄露
Bitmap使用后未调用recycle()
根据SDK的描述,调用recycle并不是必须的。但在实际使用时,Bitmap占用的内存是很大的,所以当我们不再使用时,尽量调用recycle()以释放资源。
Context泄露
这是一个很隐晦的内存泄露的情况。先让我们看一下以下代码:
private&static&Drawable&sB&&
@Override&&
protected&void&onCreate(Bundle&state)&{&&
&&super.onCreate(state);&&
&&TextView&label&=&new&TextView(this);&&
&&label.setText("Leaks&are&bad");&&
&&if&(sBackground&==&null)&{&&
&&&&sBackground&=&getDrawable(R.drawable.large_bitmap);&&
&&label.setBackgroundDrawable(sBackground);&&
&&setContentView(label);&&
在这段代码中,我们使用了一个static的Drawable对象。这通常发生在我们需要经常调用一个Drawable,而其加载又比较耗时,不希望每次加载Activity都去创建这个Drawable的情况。此时,使用static无疑是最快的代码编写方式,但是其也非常的糟糕。当一个Drawable被附加到View时,这个View会被设置为这个Drawable的callback (通过调用Drawable.setCallback()实现)。这就意味着,这个Drawable拥有一个TextView的引用,而TextView又拥有一个Activity的引用。这就会导致Activity在销毁后,内存不会被释放。
相关资讯 & & &
& (11/28/:18)
& (10/10/:25)
& (05月21日)
& (03/08/:13)
& (07/10/:11)
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款&nbsp&#8250&nbsp&nbsp&#8250&nbsp
如何检测 Android Cursor 泄漏
简介:本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时该方法同样适合于其他需要检测资源泄露的情况。&最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor
泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用。另外在工作中也常发现有些应用有 Cursor
泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bug 很长时间都没被发现。但是一旦 Cursor 泄漏累计到一定数目(通常为数百个)必然会出现无法查询数据库的情况,只有等数据库服务所在进程死掉重启才能恢复正常。通常的出错信息如下,指出某 pid 的程序打开了 866 个 Cursor 没有关闭,导致了 exception: E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid )
E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)
E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)
E JavaBinder: at dalvik.system.NativeStart.run(Native Method)1. Cursor 检测原理在 Cursor 对象被 JVM 回收运行到 finalize() 方法的时候,检测 close() 方法有没有被调用,此办法在 ContentResolver 里面也得到应用。简化后的示例代码如下:import android.database.C
&import android.database.CursorW
&import android.util.L
&public class TestCursor extends CursorWrapper {
&&&&&private static final String TAG = "TestCursor";
&&&&&private boolean mIsClosed =
&&&&&private Throwable mT
&&&&&public TestCursor(Cursor c) {
&&&&&&&&&super(c);
&&&&&&&&&mTrace = new Throwable("Explicit termination method 'close()' not called");
&&&&&@Override
&&&&&public void close() {
&&&&&&&&&mIsClosed =
&&&&&@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 Finalizers2. 使用方法对于 APP 开发人员从 GINGERBREAD 开始 Android 就提供了 StrictMode
工具协助开发人员检查是否不小心地做了一些不该有的操作。使用方法是在 Activity 里面设置
StrictMode,下面的例子是打开了检查泄漏的 SQLite 对象以及 Closeable 对象(普通
Cursor/FileInputStream 等)的功能,发现有违规情况则记录 log 并使程序强行退出。import android.os.StrictM
&public class TestActivity extends Activity {
&&&&&private static final boolean DEVELOPER_MODE =
&&&&&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) {& // !!提前返回
&&&&&cursor.close();
&}类的成员变量假设类里面有一个在类全局有效的成员变量,在方法 A 获取了查询结果,后面在其他地方又获取了一次查询结果,那么第二次查询的时候就应该先把前面一个 Cursor 对象关闭。public class TestCursor {
&&&&&private Cursor mC
&&&&&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 =
&&&&&cursor = query();
&&&&&// 中间省略某些出现异常的代码
&} catch (Exception e) {
&&&&&// 出现异常
&} finally {
&&&&&if (cursor != null)
&&&&&&&&&cursor.close();
&}4. 总结思考在 finalize() 里面检测是可行的,且基本可以满足需要。针对 finalize() 执行时间不确定以及可能不执行的问题,可以通过记录目前打开没关闭的 Cursor 数量来部分解决,超过一定数目发出警告,两种手段相结合。还有没有其他检测办法呢?有,在 Cursor 构造方法以及 close() 方法添加 log,运行一段时间后检查 log 看哪个地方没有关闭。简化代码如下:import android.database.C
&import android.database.CursorW
&import android.util.L
&public class TestCursor extends CursorWrapper {
&&&&&private static final String TAG = "TestCursor";
&&&&&private Throwable mT
&&&&&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 =
&&&&&&&&&Log.d(TAG, "Cursor " + this.hashCode() + " closed.");
&}检查时看某个 hashCode() 的 Cursor 有没有调用过 close()
方法,没有的话说明资源有泄露。这种方法优点是同样准确,且更可靠。缺点是需要检查大量
log,且打开/关闭的地方可能相距较远,如果不写个小脚本分析人工看的话会比较痛苦;另外必须 APP 完全退出后才能检查,因为后台运行时某些
Cursor 还在正常使用。
上一篇: 一、分析 1、最简单的布局:只有一个ListView 如果整个页面只有一个ListView的话,那么由于ListView本身带有滚动效果,所以当加载的数据超过页面显示的范围时,可以通过上下滑动来查看所有的item。 因此这种情况下,不需要添加ScrollView。 2、其它布局A+ Li
下一篇: 以前在eoe论坛中找过裁剪图片为圆形图片的方法,但是效果都不是很理想,这几天因为公司业务的要求,需要对头像进行裁剪以圆形 的方式显示,这个方法是根据传入的图片的高度(height)和宽度(width)决定的,如果是 width = height 时,则会裁剪高度,裁剪的Android开发(51)
本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例,同时该方法同样适合于其他需要检测资源泄露的情况,感兴趣的朋友可以了解下
&iframe id=&cproIframe_u& width=&580& height=&90& src=&/acom?adn=3&at=231&aurl=&cad=1&ccd=24&cec=GBK&cfv=18&ch=0&col=zh-CN&conBW=0&conOP=1&cpa=1&dai=2&dis=0&ltr=https%3A%2F%%2Flink%3Furl%3DicP-qwLvgn4N4Ey9Q1xtKRM0k5rShCKeIVPhaoZiuoiiOVTblFq8PBXCzAlD8fyf%26wd%3D%26eqid%3Dddb0d9efcf4&ltu=http%3A%2F%2Fwww.jb51.net%2Farticle%2F33272.htm&lu_161=0&lunum=6&n=jb51_cpr&pcs=&pis=&ps=516x246&psr=&pss=&qn=06ce965a&rad=&rsi0=580&rsi1=90&rsi5=4&rss0=%23FFFFFF&rss1=%23F7FCFF&rss2=%230000ff&rss3=%&rss4=%&rss5=&rss6=%23e10900&rss7=&scale=&skin=tabcloud_skin_3&stid=5&td_id=1892994&titFF=%E5%AE%8B%E4%BD%93&titFS=12&titTA=left&tn=text_default_580_90&tpr=8&ts=1&version=2.0&xuanting=0&dtm=BAIDU_DUP2_SETJSONADSLOT&dc=2&di=u1892994&ti=Android%E6%A3%80%E6%B5%8BCursor%E6%B3%84%E6%BC%8F%E7%9A%84%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8A%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95_Android_%E8%84%9A%E6%9C%AC%E4%B9%8B%E5%AE%B6&tt=1.90.110.111& align=&center,center& marginwidth=&0& marginheight=&0& scrolling=&no& frameborder=&0& allowtransparency=&true&&&/iframe&
本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时该方法同样适合于其他需要检测资源泄露的情况。
最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用。另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bug 很长时间都没被发现。&
但是一旦 Cursor 泄漏累计到一定数目(通常为数百个)必然会出现无法查询数据库的情况,只有等数据库服务所在进程死掉重启才能恢复正常。通常的出错信息如下,指出某 pid 的程序打开了 866 个 Cursor 没有关闭,导致了 exception:&
E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)&
E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid )&
E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)&
E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)&
E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)&
E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)&
E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)&
E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)&
E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)&
E JavaBinder: at dalvik.system.NativeStart.run(Native Method)&
1. Cursor 检测原理&
在 Cursor 对象被 JVM 回收运行到 finalize() 方法的时候,检测 close() 方法有没有被调用,此办法在 ContentResolver 里面也得到应用。简化后的示例代码如下:&
import android.database.C&
import android.database.CursorW&
import android.util.L&
public class TestCursor extends CursorWrapper {&
private static final String TAG = &TestCursor&;&
private boolean mIsClosed =&
private Throwable mT&
public TestCursor(Cursor c) {&
super(c);&
mTrace = new Throwable(&Explicit termination method 'close()' not called&);&
@Override&
public void close() {&
mIsClosed =&
@Override&
public void finalize() throws Throwable {&
if (mIsClosed != true) {&
Log.e(TAG, &Cursor leaks&, mTrace);&
} finally {&
super.finalize();&
然后查询的时候,把 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 并使程序强行退出。&
import android.os.StrictM&
public class TestActivity extends Activity {&
private static final boolean DEVELOPER_MODE =&
public void onCreate() {&
if (DEVELOPER_MODE) {&
StrictMode.setVMPolicy(new StrictMode.VMPolicy.Builder()&
.detectLeakedSqlLiteObjects()&
.detectLeakedClosableObjects()&
.penaltyLog()&
.penaltyDeath()&
.build());&
super.onCreate();&
对于 framework 开发人员&
如果是通过 ContentProvider 提供数据库数据,在 ContentResolver 里面已有 CloseGuard 类实行类似检测,但需要自行打开(上例也是打开 CloseGuard):&
1 CloseGuard.setEnabled(true);更值得推荐的办法是按照本文第一节中的检测原理,在 ContentResolver 内部类 CursorWrapperInner 里面加入。其他需要检测类似于资源泄漏的,同样可以使用该检测原理。&
3. 容易出错的地方&
忘记调用 close() 这种低级错误没什么好说的,这种应该也占不小的比例。下面说说不太明显的例子。&
有时候粗心会犯这种错误,在 close() 调用之前就 return 了,特别是函数比较大逻辑比较复杂时更容易犯错。这种情况可以通过把 close() 放在 finally 代码块解决&
private void method() {&
Cursor cursor = query(); // 假设 query() 是一个查询数据库返回 Cursor 结果的函数&
if (flag == false) { // !!提前返回&
cursor.close();&
类的成员变量&
假设类里面有一个在类全局有效的成员变量,在方法 A 获取了查询结果,后面在其他地方又获取了一次查询结果,那么第二次查询的时候就应该先把前面一个 Cursor 对象关闭。&
public class TestCursor {&
private Cursor mC&
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,导致没有跑到关闭的地方:&
Cursor cursor = query();&
// 中间省略某些出现异常的代码&
cursor.close();&
} catch (Exception e) {&
// !!出现异常没跑到 cursor.close()&
这种情况应该把 close() 放到 finally 代码块里面:&
Cursor cursor =&
cursor = query();&
// 中间省略某些出现异常的代码&
} catch (Exception e) {&
// 出现异常&
} finally {&
if (cursor != null)&
cursor.close();&
4. 总结思考&
在 finalize() 里面检测是可行的,且基本可以满足需要。针对 finalize() 执行时间不确定以及可能不执行的问题,可以通过记录目前打开没关闭的 Cursor 数量来部分解决,超过一定数目发出警告,两种手段相结合。&
还有没有其他检测办法呢?有,在 Cursor 构造方法以及 close() 方法添加 log,运行一段时间后检查 log 看哪个地方没有关闭。简化代码如下:&
import android.database.C&
import android.database.CursorW&
import android.util.L&
public class TestCursor extends CursorWrapper {&
private static final String TAG = &TestCursor&;&
private Throwable mT&
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 =&
Log.d(TAG, &Cursor & + this.hashCode() + & closed.&);&
检查时看某个 hashCode() 的 Cursor 有没有调用过 close() 方法,没有的话说明资源有泄露。这种方法优点是同样准确,且更可靠。缺点是需要检查大量 log,且打开/关闭的地方可能相距较远,如果不写个小脚本分析人工看的话会比较痛苦;另外必须 APP 完全退出后才能检查,因为后台运行时某些 Cursor 还在正常使用。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:59232次
积分:1169
积分:1169
排名:千里之外
原创:35篇
转载:150篇
(3)(4)(1)(23)(9)(11)(2)(5)(5)(9)(2)(16)(9)(4)(5)(1)(3)(13)(12)(2)(1)(14)(5)(1)(1)(2)(19)(3)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时该方法同样适合于其他需要检测资源泄露的情况。
最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用。另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bug 很长时间都没被发现。
但是一旦 Cursor 泄漏累计到一定数目(通常为数百个)必然会出现无法查询数据库的情况,只有等数据库服务所在进程死掉重启才能恢复正常。通常的出错信息如下,指出某 pid 的程序打开了 866 个 Cursor 没有关闭,导致了 exception:
E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid )
E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)
E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)
E JavaBinder: at dalvik.system.NativeStart.run(Native Method)
1. Cursor 检测原理
在 Cursor 对象被 JVM 回收运行到 finalize() 方法的时候,检测 close() 方法有没有被调用,此办法在 ContentResolver 里面也得到应用。简化后的示例代码如下:
1 import android.database.C
2 import android.database.CursorW
3 import android.util.L
5 public class TestCursor extends CursorWrapper {
private static final String TAG = "TestCursor";
private boolean mIsClosed = false;
private Throwable mT
public TestCursor(Cursor c) {
mTrace = new Throwable("Explicit termination method 'close()' not called");
public void close() {
mIsClosed = true;
public void finalize() throws Throwable {
if (mIsClosed != true) {
Log.e(TAG, "Cursor leaks", mTrace);
} finally {
super.finalize();
然后查询的时候,把 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.StrictM
3 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):
1 CloseGuard.setEnabled(true);
更值得推荐的办法是按照本文第一节中的检测原理,在 ContentResolver 内部类 CursorWrapperInner 里面加入。其他需要检测类似于资源泄漏的,同样可以使用该检测原理。
3. 容易出错的地方
忘记调用 close() 这种低级错误没什么好说的,这种应该也占不小的比例。下面说说不太明显的例子。
有时候粗心会犯这种错误,在 close() 调用之前就 return 了,特别是函数比较大逻辑比较复杂时更容易犯错。这种情况可以通过把 close() 放在 finally 代码块解决
1 private void method() {
Cursor cursor = query(); // 假设 query() 是一个查询数据库返回 Cursor 结果的函数
if (flag == false) {
// !!提前返回
cursor.close();
类的成员变量
假设类里面有一个在类全局有效的成员变量,在方法 A 获取了查询结果,后面在其他地方又获取了一次查询结果,那么第二次查询的时候就应该先把前面一个 Cursor 对象关闭。
1 public class TestCursor {
private Cursor mC
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,导致没有跑到关闭的地方:
Cursor cursor = query();
// 中间省略某些出现异常的代码
cursor.close();
5 } catch (Exception e) {
// !!出现异常没跑到 cursor.close()
这种情况应该把 close() 放到 finally 代码块里面:
1 Cursor cursor = null;
cursor = query();
// 中间省略某些出现异常的代码
5 } catch (Exception e) {
// 出现异常
7 } finally {
if (cursor != null)
cursor.close();
4. 总结思考
在 finalize() 里面检测是可行的,且基本可以满足需要。针对 finalize() 执行时间不确定以及可能不执行的问题,可以通过记录目前打开没关闭的 Cursor 数量来部分解决,超过一定数目发出警告,两种手段相结合。
还有没有其他检测办法呢?有,在 Cursor 构造方法以及 close() 方法添加 log,运行一段时间后检查 log 看哪个地方没有关闭。简化代码如下:
1 import android.database.C
2 import android.database.CursorW
3 import android.util.L
5 public class TestCursor extends CursorWrapper {
private static final String TAG = "TestCursor";
private Throwable mT
public TestCursor(Cursor c) {
mTrace = new Throwable("cusor opened here");
Log.d(TAG, "Cursor " + this.hashCode() + " opened, stacktrace is: ", mTrace);
public void close() {
mIsClosed = true;
Log.d(TAG, "Cursor " + this.hashCode() + " closed.");
检查时看某个 hashCode() 的 Cursor 有没有调用过 close() 方法,没有的话说明资源有泄露。这种方法优点是同样准确,且更可靠。缺点是需要检查大量 log,且打开/关闭的地方可能相距较远,如果不写个小脚本分析人工看的话会比较痛苦;另外必须 APP 完全退出后才能检查,因为后台运行时某些 Cursor 还在正常使用。
转载请注明出处:/imouto/archive//how-to-detect-leaked-cursor.html
本文外部镜像:/2013/01/how-to-detect-android-cursor-leak-cn.html
Views(...) Comments()

我要回帖

更多关于 c 内存泄漏检测 的文章

 

随机推荐