现在,就从cursor关闭的问题开始把,谁都知道cursor要关闭,但是往往相反,人们却常常忘记关闭,因为真正的应用场景可能并非理想化的简单。
1.理想化的cursor关闭
复制代码 代码如下:
// Sample Code
Cursor cursor = db.query();
List<String> List = convertToList(cursor);
cursor.close();
这是最简单的cursor使用场景,如果这里的cursor没有关闭,我想可能会引起万千口水,一片骂声。
但是实际场景可能并非如此,这里的cursor可能不会关闭,至少有以下两种可能。
2. Cursor未关闭的可能
(1). cursor.close()之前发生异常。
(2). cursor需要继续使用,不能马上关闭,后面忘记关闭了。
3. Cursor.close()之前发生异常
这个很容易理解,应该也是初学者最开始碰到的常见问题,举例如下:
复制代码 代码如下:
try {
Cursor c = queryCursor();
int a = c.getInt(1);
......
// 如果出错,后面的cursor.close()将不会执行
......
c.close();
} catch (Exception e) {
}
正确写法应该是:
复制代码 代码如下:
Cursor c;
try {
c = queryCursor();
int a = c.getInt(1);
......
// 如果出错,后面的cursor.close()将不会执行
//c.close();
} catch (Exception e) {
} finally{
if (c != null) {
c.close();
}
}
很简单,但是需要时刻谨记。
4. Cursor需要继续使用,不能马上关闭
有没有这种情况?怎么办?
答案是有,CursorAdapter就是一个典型的例子。
CursorAdapter示例如下:
复制代码 代码如下:
mCursor = getContentResolver().query(CONTENT_URI,PROJECTION,
null,null,null);
mAdapter = new MyCursorAdapter(this,R.layout.List_item,mCursor);
setlistadapter(mAdapter);
// 这里就不能关闭执行mCursor.close(),
// 否则List中将会无数据
5. 这样的Cursor应该什么时候关闭呢?
这是个可以说好回答也可以说不好回答的问题,那就是在Cursor不再使用的时候关闭掉。
比如说,
上面的查询,如果每次进入或者resume的时候会重新查询执行。
一般来说,也是这种需求,很少我看不到界面的时候还在不停地显示查询结果吧,如果真的有,不予讨论,记得最终关掉就OK了。
这个时候,我们一般可以在onStop()方法里面把cursor关掉。
复制代码 代码如下:
@OverrIDe
protected voID onStop() {
super.onStop();
// mCursorAdapter会释放之前的cursor,相当于关闭了cursor
mCursorAdapter.changeCursor(null);
}
我专门附上CursorAdapter的changeCursor()方法源码,让大家看的更清楚,免得不放心changeCursor(null)方法:
复制代码 代码如下:
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*
* @param cursor The new cursor to be used
*/
public voID changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor,returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)},the returned old Cursor is <em>not</em>
* closed.
*
* @param newCursor The new cursor to be used.
* @return Returns the prevIoUsly set Cursor,or null if there wasa not one.
* If the given new Cursor is the same instance is the prevIoUsly set
* Cursor,null is also returned.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
Cursor oldCursor = mCursor;
if (oldCursor != null) {
if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
if (mDataSetobserver != null) oldCursor.unregisterDataSetobserver(mDataSetobserver);
}
mCursor = newCursor;
if (newCursor != null) {
if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
if (mDataSetobserver != null) newCursor.registerDataSetobserver(mDataSetobserver);
mRowIDColumn = newCursor.getColumnIndexOrThrow("_ID");
mDataValID = true;
// notify the observers about the new cursor
notifyDataSetChanged();
} else {
mRowIDColumn = -1;
mDataValID = false;
// notify the observers about the lack of a data set
notifyDataSetInvalIDated();
}
return oldCursor;
}
6.实战AsyncqueryHandler中Cursor的关闭问题
AsyncqueryHandler是一个很经典很典型的分析Cursor的例子,不仅一阵见血,能举一反三,而且非常常见,为以后避免。
AsyncqueryHandler文档参考地址:
http://developer.androID.com/reference/androID/content/AsyncqueryHandler.HTML
下面这段代码是AndroID2.3系统中Mms信息主页面ConversationList源码的一部分,大家看看Cursor正确关闭了吗?
复制代码 代码如下:
private final class ThreadListqueryHandler extends AsyncqueryHandler {
public ThreadListqueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@OverrIDe
protected voID onqueryComplete(int token,Object cookie,Cursor cursor) {
switch (token) {
case THREAD_List_query_TOKEN:
mlistadapter.changeCursor(cursor);
setTitle(mTitle);
... ...
break;
case HAVE_LOCKED_MESSAGES_TOKEN:
long threadID = (Long)cookie;
confirmDeleteThreadDialog(new DeleteThreadListener(threadID,mqueryHandler,
ConversationList.this),threadID == -1,
cursor != null && cursor.getCount() > 0,
ConversationList.this);
break;
default:
Log.e(TAG,"onqueryComplete called with unkNown token " + token);
}
}
}
复制代码 代码如下:
@OverrIDe
protected voID onStop() {
super.onStop();
mlistadapter.changeCursor(null);
}
大家觉得有问题吗?
主要是两点:
(1). THREAD_List_query_TOKEN分支的Cursor正确关闭了吗?
(2). HAVE_LOCKED_MESSAGES_TOKEN分支的Cursor正确关闭了吗?
根据前面的一条条分析,答案是:
(1). THREAD_List_query_TOKEN分支的Cursor被传递到了mlistadapter了,而mlistadapter在onStop里面使用changeCursor(null),当用户离开当前Activity,这个Cursor被正确释放了,不会泄露。
(2). HAVE_LOCKED_MESSAGES_TOKEN分支的Cursor(就是参数cursor),只是作为一个判断的一个条件,被使用后不再使用,但是也没有关掉,所以cursor泄露,在StrictMode监视下只要跑到这个地方都会抛出这个错误:
E/StrictMode(639): A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoIDing resource leaks.
E/StrictMode(639): java.lang.Throwable: Explicit termination method 'close' not called
E/StrictMode(639): at dalvik.system.CloseGuard.open(CloseGuard.java:184)
... ...
在AndroID.0 JellyBean中谷歌修正了这个泄露问题,相关代码如下:
复制代码 代码如下:
private final class ThreadListqueryHandler extends ConversationqueryHandler {
public ThreadListqueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@OverrIDe
protected voID onqueryComplete(int token,Cursor cursor) {
switch (token) {
case THREAD_List_query_TOKEN:
mlistadapter.changeCursor(cursor);
... ...
break;
case UNREAD_THREADS_query_TOKEN:
// 新增的UNREAD_THREADS_query_TOKEN分子和HAVE_LOCKED_MESSAGES_TOKEN分支也是类似的情况,cursor在jellybean中被及时关闭了
int count = 0;
if (cursor != null) {
count = cursor.getCount();
cursor.close();
}
mUnreadConvCount.setText(count > 0 ? Integer.toString(count) : null);
break;
case HAVE_LOCKED_MESSAGES_TOKEN:
@SuppressWarnings("unchecked")
Collection<Long> threadIDs = (Collection<Long>)cookie;
confirmDeleteThreadDialog(new DeleteThreadListener(threadIDs,threadIDs,
ConversationList.this);
// HAVE_LOCKED_MESSAGES_TOKEN分支中的cursor在jellybean中被及时关闭了
if (cursor != null) {
cursor.close();
}
break;
default:
Log.e(TAG,"onqueryComplete called with unkNown token " + token);
}
}
}
复制代码 代码如下:
@OverrIDe
protected voID onStop() {
super.onStop();
mlistadapter.changeCursor(null);
}
是不是小看了AsyncqueryHandler,谷歌在早期的版本里面都有一些这样的代码,更何况不注意的我们呢,实际上网上很多使用AsyncqueryHandler举例中都犯了这个错误,看完这篇文章后,以后再也不怕AsyncqueryHandler的cursor泄露了,还说不定能解决很多你现在应用的后台strictmode的cursor not close异常问题。
7.小结
虽然我觉得还有很多cursor未关闭的情况没有说到,但是根本问题都是及时正确的关闭cursor。
内存泄露cursor篇是我工作经验上的一个总结,专门捋清楚后对我自己对大家觉得都很有帮助,让复杂的问题本质化,简单化!
总结
以上是内存溢出为你收集整理的Android App调试内存泄露之Cursor篇全部内容,希望文章能够帮你解决Android App调试内存泄露之Cursor篇所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)