亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android App調試內存泄露之Cursor篇

2020-04-11 12:42:13
字體:
來源:轉載
供稿:網友
最近在工作中處理了一些內存泄露的問題,在這個過程中我尤其發現了一些基本的問題反而忽略導致內存泄露,比如靜態變量,cursor關閉,線程,定時器,反注冊,bitmap等等,我稍微統計并總結了一下,當然了,這些問題這么說起來比較籠統,接下來我會根據問題,把一些實例代碼貼出來,一步一步分析,在具體的場景下,用行之有效的方法,找出泄露的根本原因,并給出解決方案。
現在,就從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, 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);
}

是不是小看了AsyncQueryHandler,谷歌在早期的版本里面都有一些這樣的代碼,更何況不注意的我們呢,實際上網上很多使用AsyncQueryHandler舉例中都犯了這個錯誤,看完這篇文章后,以后再也不怕AsyncQueryHandler的cursor泄露了,還說不定能解決很多你現在應用的后臺strictmode的cursor not close異常問題。

7.小結
雖然我覺得還有很多cursor未關閉的情況沒有說到,但是根本問題都是及時正確的關閉cursor。
內存泄露cursor篇是我工作經驗上的一個總結,專門捋清楚后對我自己對大家覺得都很有幫助,讓復雜的問題本質化,簡單化!
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久精品99久久香蕉国产色戒| 亚洲欧美国产精品va在线观看| 97精品在线视频| 国产精品一区二区三区久久| 日韩精品在线视频美女| 亚洲韩国青草视频| 亚洲精品美女在线观看| 亚洲欧美精品伊人久久| 91国内精品久久| 日韩欧美国产中文字幕| 欧美激情久久久久久| 国产精品偷伦免费视频观看的| 国产精品96久久久久久又黄又硬| yw.139尤物在线精品视频| 色婷婷久久一区二区| 欧美成人黄色小视频| 久久av资源网站| 国产精品久久久久高潮| 国产精品成人观看视频国产奇米| 亚洲综合一区二区不卡| 国产日韩欧美中文在线播放| 7m精品福利视频导航| 精品成人久久av| www.亚洲人.com| 亚洲一区二区三区sesese| 久久在线免费观看视频| 日韩av在线天堂网| 91久久精品国产91性色| 欧美俄罗斯性视频| 精品精品国产国产自在线| 国产高清视频一区三区| 中文字幕欧美视频在线| 亚洲女人天堂网| 夜夜狂射影院欧美极品| 伊人久久综合97精品| 日韩美女写真福利在线观看| 欧美日韩国产区| 欧美激情久久久久久| 亚洲国产精品嫩草影院久久| 日产精品99久久久久久| 欧美一区二三区| 国产在线精品成人一区二区三区| 亚洲性线免费观看视频成熟| 91亚洲精品在线| 综合国产在线观看| 中文字幕亚洲欧美一区二区三区| 日韩国产在线看| 91成人在线播放| 美日韩精品视频免费看| 欧美在线国产精品| 欧美影院久久久| 欧美最近摘花xxxx摘花| 91成人福利在线| 97免费在线视频| 久久精品国产亚洲7777| 欧美—级高清免费播放| 久久免费高清视频| 欧美尺度大的性做爰视频| 欧美国产日韩在线| 91中文字幕一区| 最新69国产成人精品视频免费| 日韩精品亚洲视频| 欧美激情精品久久久久久蜜臀| 久久精品免费电影| 8090成年在线看片午夜| 日韩最新免费不卡| 伊人久久久久久久久久久| 日本韩国在线不卡| 久久综合88中文色鬼| 国产人妖伪娘一区91| 7777精品视频| 韩国三级电影久久久久久| 黑人巨大精品欧美一区二区三区| 国产日韩欧美91| 欧美最猛黑人xxxx黑人猛叫黄| 欧美精品一区二区三区国产精品| 亚洲福利视频免费观看| 国产精品久久久久99| 91高潮在线观看| 欧美福利在线观看| 亚洲国产日韩欧美在线图片| 日韩在线观看免费高清完整版| 欧美福利在线观看| 欧美国产日韩一区二区| 国产乱肥老妇国产一区二| 亚洲精品国产综合久久| 有码中文亚洲精品| 国产精品美女久久久免费| 亚洲国产精品女人久久久| 久久精品一区中文字幕| 宅男66日本亚洲欧美视频| 久久久久久综合网天天| 操人视频在线观看欧美| 日韩av在线播放资源| 国外视频精品毛片| 久久国内精品一国内精品| 亚洲色图校园春色| 亚洲精品乱码久久久久久金桔影视| 色综合久久精品亚洲国产| 国产精品爽爽ⅴa在线观看| 国产精品女人久久久久久| 国产激情久久久久| 欧美日本精品在线| www.国产一区| 久久好看免费视频| yellow中文字幕久久| 成人xxxxx| 久久99精品久久久久久噜噜| 777午夜精品福利在线观看| 亚洲国产成人精品一区二区| 中文字幕欧美精品在线| 亚洲精品自产拍| 国产精品一区二区三区毛片淫片| 日韩中文字幕在线免费观看| 97高清免费视频| 最近2019免费中文字幕视频三| 欧美日韩亚洲精品内裤| 黑人巨大精品欧美一区二区| 国产精品亚洲片夜色在线| 日韩电影大全免费观看2023年上| 欧美精品一区三区| 92国产精品久久久久首页| 日韩成人av在线播放| 日韩欧美国产一区二区| 精品视频一区在线视频| 日韩精品一区二区视频| 日韩电影大片中文字幕| 秋霞午夜一区二区| 久久综合九色九九| 欧美成人激情视频免费观看| 黄色精品在线看| 久久综合久久美利坚合众国| 91国自产精品中文字幕亚洲| 亚洲国产精品中文| 国产91露脸中文字幕在线| 8050国产精品久久久久久| 色中色综合影院手机版在线观看| 欧美精品video| 中文字幕欧美精品日韩中文字幕| 热re91久久精品国99热蜜臀| 国产亚洲精品一区二区| 日韩欧美精品在线观看| 欧美综合在线第二页| 欧美在线一区二区三区四| 欧美精品在线视频观看| 国产精品一区二区久久久| 黑人极品videos精品欧美裸| 一道本无吗dⅴd在线播放一区| 欧美噜噜久久久xxx| 欧美激情视频在线免费观看 欧美视频免费一| 宅男66日本亚洲欧美视频| 国产一区深夜福利| 国产一区二区三区在线播放免费观看| 这里只有精品在线观看| 91成人国产在线观看| 色偷偷888欧美精品久久久| 成人精品久久一区二区三区| 亚洲精品自拍偷拍| 精品国产美女在线| 69av成年福利视频| 欧美日韩在线免费观看| 国产精品白嫩初高中害羞小美女| 日韩欧美视频一区二区三区| 亚洲xxxxx电影|