Android 2.3 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);
}

附上CursorAdapter的changeCursor()方法源码:

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

主要是两点:

    (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)
... ...
 

  在Android4.0 JellyBean中谷歌修正了这个泄露问题,相关代码如下:

private final class ThreadListQueryHandler extends ConversationQueryHandler {
    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);
 
            ... ...
 
            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, mQueryHandler,
                    ConversationList.this), threadIds,
                    cursor != null && cursor.getCount() > 0,
                    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);
}
 

 StrictMode

     http://developer.android.com/reference/android/os/StrictMode.html   

     Android 2.3起,新增加了一个新的类,叫StrictMode(android.os.StrictMode)。这个类可以用来帮助开发者改进他们编写的应用,并且提供了各种的策略,这些策略能随时检查和报告开发者开发应用中存在的问题,比如可以监视那些本不应该在主线程中完成的工作或者其他的一些不规范和不好的代码。

     主要检测了读写操作,访问网络,数据库读写等耗时的操作并将其以log或者dialog等形式打印出来。分析这些日志,我们可以尽快找出程序运行缓慢的原因进而优化代码,避免ANR(Application Not Responding)窗口的出现。

  StrictMode有多种不同的策略,每一种策略又有不同的规则,当开发者违背某个规则时,每个策略都有不同的方法去显示提醒用户。

Example code to enable from early in your ApplicationActivity, or other application component's onCreate() method:

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }

参考链接:

http://www.cnblogs.com/qianxudetianxia/archive/2012/11/19/2757376.html 

原文地址:https://www.cnblogs.com/willyan/p/2980459.html