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;
}