Android App debugging memory leaks: Cursor

  • 2020-05-05 11:51:55
  • OfStack

Recent work in dealing with some of the memory leak problem, in the process, I especially found some basic problems instead of ignoring cause memory leaks, such as static variables, cursor closed, threads, timer, registration, bitmap etc, I statistics and summarized briefly, and, of course, these problems is more general, so I'll according to the problems, stick some example code, step by step analysis, in specific scenarios, with effective method, find out the root cause of the leak, and gives the solution.
Now, let's start with the cursor shutdown. Everyone knows that cursor is going to be closed, but on the contrary, people often forget to close it, because real application scenarios may not be idealized.
1. Idealized cursor close
 
// Sample Code 
Cursor cursor = db.query(); 
List<String> list = convertToList(cursor); 
cursor.close(); 

This is the simplest cursor use scenario, if the cursor here is not closed, I think it may cause thousands of saliva, a lot of abuse.
But that may not be the case, and cursor here may not be closed, at least in two ways.
2. Cursor not closed possible
(1) an exception occurred before. cursor.close().
(2). cursor needs to continue to be used. It cannot be closed immediately.

3. Cursor.close() before the abnormal
This is easy to understand and should be a common problem for beginners at the beginning, for example:
 
try { 
Cursor c = queryCursor(); 
int a = c.getInt(1); 
...... 
//  If the error , At the back of the cursor.close() Will not be executed  
...... 
c.close(); 
} catch (Exception e) { 
} 

The correct way to write it is:
 
Cursor c; 
try { 
c = queryCursor(); 
int a = c.getInt(1); 
...... 
//  If the error , At the back of the cursor.close() Will not be executed  
//c.close(); 
} catch (Exception e) { 
} finally{ 
if (c != null) { 
c.close(); 
} 
}  

It's simple, but it's important to keep in mind.
4. Cursor needs to be continued.
cannot be turned off immediately Is that the case? How to do?
The answer is yes, 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); 
//  You can't close execution here mCursor.close(), 
//  Otherwise, list There will be no data  

5. When should such Cursor be closed?
This is a question that can be answered either well or badly, which is to close Cursor when it is no longer in use.
For example,
The above query, if entered or resume each time the query will be re-executed.
Generally speaking, this is also the requirement. It is rare that I can't see the interface and still keep displaying the query results. If there is one, I will not discuss it.
At this point, we can usually turn cursor off in the onStop() method.
 
@Override 
protected void onStop() { 
super.onStop(); 
// mCursorAdapter Will release before cursor It's kind of closed cursor 
mCursorAdapter.changeCursor(null); 
} 

I specially attached CursorAdapter changeCursor() method source code, let you see more clearly, lest you worry 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. Actual Cursor closure problem in AsyncQueryHandler
AsyncQueryHandler is a very classic and typical example of analyzing Cursor. Not only can you see the blood in a flash, but it is also very common and can be avoided in the future.
AsyncQueryHandler documentation reference address:
http://developer.android.com/reference/android/content/AsyncQueryHandler.html
The following code is Android2.3 system Mms information home page ConversationList source part, we see Cursor 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); 
} 

Any questions?
is mainly composed of two points: :
(1). THREAD_LIST_QUERY_TOKEN branch is Cursor closed correctly?
(2). HAVE_LOCKED_MESSAGES_TOKEN branch is Cursor closed correctly?
According to the previous analysis, the answer is:
(1). THREAD_LIST_QUERY_TOKEN branch Cursor is passed to mListAdapter. mListAdapter USES changeCursor(null) in onStop.
(2). HAVE_LOCKED_MESSAGES_TOKEN branch Cursor (which is the parameter cursor) is only used as a condition of judgment. It is no longer used after being used, but it is not turned off
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)
... ...

Google fixes this leak in Android. 0 JellyBean, with the following code:
 
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: 
//  The addition of UNREAD_THREADS_QUERY_TOKEN Molecular and HAVE_LOCKED_MESSAGES_TOKEN Branching is a similar thing, cursor in jellybean It was shut down in time  
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 In the branches cursor in jellybean It was shut down in time  
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); 
} 

Isn't look down upon the AsyncQueryHandler, Google in early version has some of this code, and do not pay attention to us, in fact many online use AsyncQueryHandler made the mistake, for example, after watching this article, later any further not afraid AsyncQueryHandler cursor spilled the, still perhaps can solve a lot of you will now use the backend strictmode cursor not close abnormal problem.

7. Summary
Although I think there are many cases where cursor has not been shut down, the fundamental problem is to shut down cursor timely and correctly.
Memory leak cursor is a summary of my work experience, which is helpful to me and everyone. It makes complex problems essential and simple.

Related articles: