Android Appデバッグメモリ漏洩のCursor編

8917 ワード

最近仕事の中でいくつかのメモリの漏洩の問題を処理して、この過程の中で私は特にいくつかの基本的な問題を発見してかえってメモリの漏洩を招くことを無視して、例えば静的な変数、cursorが閉じて、スレッド、タイマー、逆登録、bitmapなど、私は少し統計して総括して、もちろん、これらの問題はこのように言って比較的に漠然として、次に私は問題に基づいて、いくつかの実例のコードを貼り出して、一歩一歩分析して、具体的なシーンの下で、行の有効な方法で、漏洩の根本的な原因を探し出して、そして解決策を提供します.
今、cursorが閉じる問題から始めて、誰もがcursorが閉じることを知っていますが、逆に、人々はよく閉じることを忘れています.本当のアプリケーションシーンは理想的な簡単ではないかもしれません.
1.理想化されたcursorクローズ
 
  
// Sample Code
Cursor cursor = db.query();
List list = convertToList(cursor);
cursor.close();

これは最も簡単なcursorの使用シーンで、もしここのcursorが閉じていなければ、私は万千のよだれを引き起こして、悪口を言うかもしれないと思います.
しかし、実際のシーンはそうではないかもしれません.ここでcursorは閉じないかもしれません.少なくとも以下の2つの可能性があります.
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 not
* 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
次のコードはAndroid 2です.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);
}

皆さんは問題があると思いますか?
主に2点です.
(1). THREAD_LIST_QUERY_TOKENブランチのカーサーは正しく閉じましたか?
(2). HAVE_LOCKED_MESSAGES_TOKENブランチのカーサーは正しく閉じましたか?
前の分析によると、答えは次のとおりです.
(1). THREAD_LIST_QUERY_TOKENブランチのCursorはmListAdapterに渡され、mListAdapterはonStopでchangeCursor(null)を使用し、ユーザーが現在のActivityを離れると、このCursorは正しく解放され、漏洩しません.
(2). HAVE_LOCKED_MESSAGES_TOKENブランチのCursor(パラメータcursor)は、判断の1つの条件として使用された後は使用されないが、オフになっていないため、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でGoogleはこの流出問題を修正しました.関連コードは以下の通りです.
 
  
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 threadIds = (Collection)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);
}

AsyncQueryHandlerを軽蔑しているのではないでしょうか.グーグルは初期のバージョンではこのようなコードをいくつか持っていました.まして注意していない私たちは、実際にインターネット上でAsyncQueryHandlerを使って例を挙げることが多いので、この文章を読んでから、AsyncQueryHandlerのcursorが漏れることを恐れません.また、あなたが今応用しているバックグラウンドstrictmodeのcursor not closeの異常な問題を多く解決できるかもしれません.
7.まとめ
まだcursorが閉じていないことはたくさんあると思いますが、根本的な問題はcursorをタイムリーに正しく閉じることです.
メモリ漏洩cursor編は私の仕事の経験上の総括で、専門的に撫でてから私自身に対してみんなにとても役に立つと思って、複雑な問題を本質化して、簡単化します!