Android Learning Series (32)--Cursor of App Debugging Memory Leaks

This article is reproduced from: https://www.cnblogs.com/qianxudetianxia/archive/2012/11/19/2757376.html Author: qianxudetianxia Please indicate the statement when reprinting.

    Recently, I have dealt with some memory leak problems in my work. During this process, I especially found some basic problems that lead to memory leaks by ignoring them, such as static variables, cursor closing, stream closing, thread, timer, anti-registration, bitmap, etc. Wait, I made some statistics and summed it up. Of course, these problems are relatively general. Next, I will post some example codes according to the problems and analyze them step by step. In specific scenarios, use the effective methods method to find out the root cause of the leak and give a solution.
    Now, let's start with the issue of closing the cursor. Everyone knows that the cursor needs to be closed, but on the contrary, people often forget to close it, because the real application scenario may not be ideally simple.
1. Ideal cursor closure

// Sample Code
Cursor cursor = db.query();
List<String> list = convertToList(cursor);
cursor.close();

    This is the simplest usage scenario of the cursor. If the cursor here is not closed, I think it may cause thousands of saliva and a lot of scolding.
    But the actual scene may not be the case, the cursor here may not be closed, there are at least the following two possibilities.

2. It is possible that the Cursor is not closed
     (1). An exception occurs before cursor.close().
     (2). The cursor needs to continue to be used and cannot be closed immediately, and forgot to close later.

3. It is easy to understand that an exception occurs before Cursor.close()
     , and it should also be a common problem encountered by beginners at the beginning, for example:

try {  
Cursor c = queryCursor();
int a = c.getInt(1);
......
// 如果出错,后面的cursor.close()将不会执行
......
c.close();
} catch (Exception e) {
}

  The correct spelling should be:

Cursor c;
try {
c = queryCursor();
int a = c.getInt(1);
......
// 如果出错,后面的cursor.close()将不会执行
//c.close();
} catch (Exception e) {
} finally{
if (c != null) {
c.close();
}
} 

    It's simple, but needs to be kept in mind at all times.

4. Cursor needs to continue to be used and cannot be closed immediately.
    Is there such a situation? How to do?
    The answer is yes, and CursorAdapter is a typical example.
    An example of CursorAdapter is as follows:

mCursor = getContentResolver().query(CONTENT_URI, PROJECTION,
null, null, null);
mAdapter = new MyCursorAdapter(this, R.layout.list_item, mCursor);
setListAdapter(mAdapter);
// 这里就不能关闭执行mCursor.close(),
// 否则list中将会无数据

5. When should such a Cursor be closed?
    This is a question that can be said to be a good or bad answer, and that is to close the Cursor when it is no longer in use.
    For example,
    the above query, if every time you enter or resume, the query will be re-executed.
    Generally speaking, it is only this kind of demand. It is rarely necessary to continuously display the query results when the interface is not visible. If there is, it will not be discussed. Remember to turn it off eventually.
    At this time, we can generally turn off the cursor in the onStop() method (meaning that you may need to re-query in onResume() or onStart()).

    @Override
protected void onStop() {
super.onStop();
// mCursorAdapter会释放之前的cursor,相当于关闭了cursor
mCursorAdapter.changeCursor(null);
}

  I specially attach the source code of the changeCursor() method of CursorAdapter, so that everyone can see it more clearly, so as not to worry about the changeCursor(null) method:

    /**
* 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. The closing problem of Cursor in actual combat AsyncQueryHandler
    AsyncQueryHandler is a very classic and typical example of analyzing Cursor.
    AsyncQueryHandler document reference address:
    http://developer.android.com/reference/android/content/AsyncQueryHandler.html
    The following code is part of the source code of the ConversationList of the Mms information main page in the Android2.3 system. Let's see that Cursor is closed correctly ?

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

    Do you think there is a problem?
    There are two main points:
    (1). Is the Cursor of the THREAD_LIST_QUERY_TOKEN branch closed correctly?
    (2). Is the Cursor of the HAVE_LOCKED_MESSAGES_TOKEN branch closed correctly?
    According to the previous analysis, the answer is:
    (1). The Cursor of the THREAD_LIST_QUERY_TOKEN branch is passed to the mListAdapter, and the mListAdapter uses changeCursor(null) in onStop. When the user leaves the current Activity, the Cursor is closed correctly, and it will not Give way.
    (2). The Cursor of the HAVE_LOCKED_MESSAGES_TOKEN branch (that is, the parameter cursor) is only used as a condition for judgment. It is no longer used after being used, but it is not turned off, so the cursor is leaked, and it will be thrown whenever it runs to this place under the monitoring of StrictMode. gives this error:

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

  In Android 4.0 JellyBean, Google fixed this leak, and the relevant code is as follows:

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

  Have you underestimated AsyncQueryHandler? Google has some such codes in earlier versions, let alone us who didn’t pay attention. In fact, many examples of using AsyncQueryHandler on the Internet have made this mistake. After reading this article, in the future No longer afraid of the leakage of the cursor of AsyncQueryHandler, and maybe it can solve many of the cursor not close exceptions in the background strictmode of your current application.

7. Summary
    Although I think there are still many cases where the cursor is not closed, the fundamental problem is to close the cursor in a timely and correct manner.
    The memory leak cursor article is a summary of my work experience. After clearing it out, it is very helpful to me and everyone, and to make complex problems essential and simple!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324140019&siteId=291194637