bug处理

Cursor泄露的处理

2017-04-27  本文已影响0人  tiger桂

cursor出现泄露时的log

cursor泄露的检测

解决cursor泄露

1. cursor出现泄露时的log

如果有cursor未被关闭,在logcat打印出来的log中有可能出现如下的信息:

例如,异常信息 ERROR/Cursor(line number): android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened hereFinalizing a Cursor that has not been deactivated or closed.

CursorWrapperInner: Cursor finalized without prior close(),这个是一个警告,提醒有未关闭的cursor。

JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=1000, 打开的cursor太多,导致无法再为新的cursor分配空间。

还有一种非常隐蔽的形式,如果单个cursor的数据量非常小,即使打开非常多的cursor也不会出现内存不足的情况。但是却会报出Too many open files的错误。在linux系统中,文件描述符是有上限限制的,在android中默认是1024个。如果跨进程调用每个cursor都会打开一块共 享内存,这就需要使用一个文件描述符,当文件描述符资源用尽时,就再也无法打开新的文件。

2.cursor泄露的检测

如果加入如下代码,将会在严格模式的(StrictMode)策略中加入检测泄露的策略。

importandroid.os.StrictMode;

publicclassTestActivityextendsActivity {

privatestaticfinalbooleanDEVELOPER_MODE =true;

publicvoidonCreate() {

if(DEVELOPER_MODE) {

StrictMode.setVmPolicy(new VmPolicy.Builder()

.detectLeakedSqlLiteObjects()

.detectLeakedClosableObjects()

.penaltyLog()

.penaltyDeath()

.build());}

super.onCreate();

}

}

如果是framework开发,还可以使用如下代码,效果和上述代码类似。

closeguard.setEnable(true);

如果存在没有关闭的cursor,将会抛出如下异常。这样就能很轻松地知道自己的应用是否存在cursor泄露。

但是,这里并没有报出泄露的cursor更详细的信息,因此仅能知道出现了问题,却不知道问题出现在哪。

java.lang.Throwable: Explicit termination method 'close' not called

at dalvik.system.CloseGuard.open(CloseGuard.java:184)

at android.content.ContentResolver$CursorWrapperInner.(ContentResolver.java:2496)

at android.content.ContentResolver.query(ContentResolver.java:509)

at android.content.ContentResolver.query(ContentResolver.java:429)

at android.content.AsyncQueryHandler$WorkerHandler.handleMessage(AsyncQueryHandler.java:79)

at android.os.Handler.dispatchMessage(Handler.java:111)

at android.os.HandlerThread.run(HandlerThread.java:61)

那么如何发现cursor在哪里泄露呢?一个简单的方法,在打开和关闭的地方打上log,通过log来发现是否成对

出现打开和关闭的操作。但是当程序逻辑变得非常复杂,尤其是在各种回调函数和多线程运行的环境下时,往往难于

发现问题。另一个方法是,可以在创建一个全局的hashMap,每次获取新的cursor时将cursor加入map,然后在程序退出

等适当的时机打印cursor的状态,哪些cursor没有close将一目了然。这个方法能比较快的定位到问题点,但是却要

找到每一个cursor的调用点,当程序中有非常多的cursor时,找到他们并逐个修改代码也是很痛苦而费时的事情。

如果是在framework中开发,那么framework倒是提供一个QueryHistory dumpQueryHistory

3. 解决cursor泄露

1)最简单的就是使用后要记得调用close方法关闭。

2)如果cursor生命周期过长,调用的逻辑很复杂,就很容易无法判断关闭的时机。所以应该尽量减少cursor的生命周期,尽快关闭。

3)有时候cursor的数据必须要在很长时间都要用到,而且调用逻辑就是很复杂,这个问题如何处理?没关系,android提供了一个很方便的类,android.database.MatrixCursor, 这个类用于构造一个cursor在内存中的拷贝,拷贝完后可以直接关闭原来的cursor,而这个内存中的MatrixCursor就不用关心它是否关闭了(除非你又给它设置了回调),它的回收仅仅依赖于java的回收机制。具体如何使用,请看下面这个复制cursor的方法。

public staticMatrixCursormatrixCursorFromCursor(Cursorcursor) {

if(cursor ==null) {

return null;

}

String[]columnNames= cursor.getColumnNames();

if(columnNames==null) {

columnNames=newString[] {};

}

MatrixCursor newCursor=newMatrixCursor(columnNames);

intnumColumns= cursor.getColumnCount();

String data[] =newString[numColumns];

cursor.moveToPosition(-1);

while(cursor.moveToNext()) {

for(inti=0;i

data[i] = cursor.getString(i);

}

newCursor.addRow(data);

}

returnnewCursor;

}

上一篇下一篇

猜你喜欢

热点阅读