Android 消息機(jī)制以及handler的內(nèi)存泄露
Handler
每個(gè)初學(xué)Android開(kāi)發(fā)的都繞不開(kāi)Handler這個(gè)“坎”,為什么說(shuō)是個(gè)坎呢,首先這是Android架構(gòu)的精髓之一,其次大部分人都是知其然卻不知其所以然。今天看到Handler.post這個(gè)方法之后決定再去翻翻源代碼梳理一下Handler的實(shí)現(xiàn)機(jī)制。
異步更新UI
先來(lái)一個(gè)必背口訣“主線程不做耗時(shí)操作,子線程不更新UI”,這個(gè)規(guī)定應(yīng)該是初學(xué)必知的,那要怎么來(lái)解決口訣里的問(wèn)題呢,這時(shí)候Handler就出現(xiàn)在我們面前了(AsyncTask也行,不過(guò)本質(zhì)上還是對(duì)Handler的封裝),來(lái)一段經(jīng)典常用代碼(這里忽略內(nèi)存泄露問(wèn)題,我們后面再說(shuō)):
首先在Activity中新建一個(gè)handler:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: mTestTV.setText("This is handleMessage");//更新UI break; } } };
然后在子線程里發(fā)送消息:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子線程有一段耗時(shí)操作,比如請(qǐng)求網(wǎng)絡(luò) mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
至此完成了在子線程的耗時(shí)操作完成后在主線程異步更新UI,可是并沒(méi)有用上標(biāo)題的post,我們?cè)賮?lái)看post的版本:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子線程有一段耗時(shí)操作,比如請(qǐng)求網(wǎng)絡(luò) Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { mTestTV.setText("This is post");//更新UI } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
從表面上來(lái)看,給post方法傳了個(gè)Runnable,像是開(kāi)了個(gè)子線程,可是在子線程里并不能更新UI啊,那么問(wèn)題來(lái)了,這是怎么個(gè)情況呢?帶著這個(gè)疑惑,來(lái)翻翻Handler的源碼:
先來(lái)看看普通的sendEmptyMessage是什么樣子:
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
將我們傳入的參數(shù)封裝成了一個(gè)消息,然后調(diào)用sendMessageDelayed:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
再調(diào)用sendMessageAtTime:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
好了,我們?cè)賮?lái)看post():
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是兩種發(fā)送消息的不同之處 }
方法只有一句,內(nèi)部實(shí)現(xiàn)和普通的sendMessage是一樣的,但是只有一點(diǎn)不同,那就是 getPostMessage(r) 這個(gè)方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
這個(gè)方法我們發(fā)現(xiàn)也是將我們傳入的參數(shù)封裝成了一個(gè)消息,只是這次是m.callback = r,剛才是msg.what=what,至于Message的這些屬性就不看了
Android消息機(jī)制
看到這里,我們只是知道了post和sendMessage原理都是封裝成Message,但是還是不清楚Handler的整個(gè)機(jī)制是什么樣子,繼續(xù)探究下去。
剛才看到那兩個(gè)方法到最終都調(diào)用了sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
這個(gè)方法又調(diào)用了 enqueueMessage,看名字應(yīng)該是把消息加入隊(duì)列的意思,點(diǎn)進(jìn)去看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
mAsynchronous這個(gè)異步有關(guān)的先不管,繼續(xù)將參數(shù)傳給了queue的enqueueMessage方法,至于那個(gè)msg的target的賦值我們后面再看,現(xiàn)在繼續(xù)進(jìn)入MessageQueue類的enqueueMessage方法,方法較長(zhǎng),我們看看關(guān)鍵的幾行:
Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg;
果然像方法名說(shuō)的一樣,一個(gè)無(wú)限循環(huán)將消息加入到消息隊(duì)列中(鏈表的形式),但是有放就有拿,這個(gè)消息怎樣把它取出來(lái)呢?
翻看MessageQueue的方法,我們找到了next(),代碼太長(zhǎng),不贅述,我們知道它是用來(lái)把消息取出來(lái)的就行了。不過(guò)這個(gè)方法是在什么地方調(diào)用的呢,不是在Handler中,我們找到了Looper這個(gè)關(guān)鍵人物,我叫他環(huán)形使者,專門負(fù)責(zé)從消息隊(duì)列中拿消息,關(guān)鍵代碼如下:
for (;;) { Message msg = queue.next(); // might block ... msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); }
簡(jiǎn)單明了,我們看到了我們剛才說(shuō)的msg.target,剛才在Handler中賦值了msg.target=this,所以我們來(lái)看Handler中的dispatchMessage:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
1.msg的callback不為空,調(diào)用handleCallback方法(message.callback.run())
2.mCallback不為空,調(diào)用mCallback.handleMessage(msg)
3.最后如果其他都為空,執(zhí)行Handler自身的 handleMessage(msg) 方法
msg的callback應(yīng)該已經(jīng)想到是什么了,就是我們通過(guò)Handler.post(Runnable r)傳入的Runnable的run方法,這里就要提提java基礎(chǔ)了,直接調(diào)用線程的run方法相當(dāng)于是在一個(gè)普通的類調(diào)用方法,還是在當(dāng)前線程執(zhí)行,并不會(huì)開(kāi)啟新的線程。
所以到了這里,我們解決了開(kāi)始的疑惑,為什么在post中傳了個(gè)Runnable還是在主線程中可以更新UI。
繼續(xù)看如果msg.callback為空的情況下的mCallback,這個(gè)要看看構(gòu)造方法:
1. public Handler() { this(null, false); } 2. public Handler(Callback callback) { this(callback, false); } 3. public Handler(Looper looper) { this(looper, null, false); } 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 5. public Handler(boolean async) { this(null, async); } 6. public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
具體的實(shí)現(xiàn)就只有最后兩個(gè),已經(jīng)知道m(xù)Callback是怎么來(lái)的了,在構(gòu)造方法中傳入就行。
最后如果這兩個(gè)回調(diào)都為空的話就執(zhí)行Handler自身的handleMessage(msg)方法,也就是我們熟知的新建Handler重寫(xiě)的那個(gè)handleMessage方法。
Looper
看到了這里有一個(gè)疑惑,那就是我們?cè)谛陆℉andler的時(shí)候并沒(méi)有傳入任何參數(shù),也沒(méi)有哪里顯示調(diào)用了Looper有關(guān)方法,那Looper的創(chuàng)建以及方法調(diào)用在哪里呢?其實(shí)這些東西Android本身已經(jīng)幫我們做了,在程序入口ActivityThread的main方法里面我們可以找到:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ...
總結(jié)
已經(jīng)大概梳理了一下Handler的消息機(jī)制,以及post方法和我們常用的sendMessage方法的區(qū)別。來(lái)總結(jié)一下,主要涉及四個(gè)類Handler、Message、MessageQueue、Looper:
新建Handler,通過(guò)sendMessage或者post發(fā)送消息,Handler調(diào)用sendMessageAtTime將Message交給MessageQueue
MessageQueue.enqueueMessage方法將Message以鏈表的形式放入隊(duì)列中
Looper的loop方法循環(huán)調(diào)用MessageQueue.next()取出消息,并且調(diào)用Handler的dispatchMessage來(lái)處理消息
在dispatchMessage中,分別判斷msg.callback、mCallback也就是post方法或者構(gòu)造方法傳入的不為空就執(zhí)行他們的回調(diào),如果都為空就執(zhí)行我們最常用重寫(xiě)的handleMessage。
最后談?wù)刪andler的內(nèi)存泄露問(wèn)題
再來(lái)看看我們的新建Handler的代碼:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... } };
當(dāng)使用內(nèi)部類(包括匿名類)來(lái)創(chuàng)建Handler的時(shí)候,Handler對(duì)象會(huì)隱式地持有Activity的引用。
而Handler通常會(huì)伴隨著一個(gè)耗時(shí)的后臺(tái)線程一起出現(xiàn),這個(gè)后臺(tái)線程在任務(wù)執(zhí)行完畢后發(fā)送消息去更新UI。然而,如果用戶在網(wǎng)絡(luò)請(qǐng)求過(guò)程中關(guān)閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時(shí)被回收掉,但由于這時(shí)線程尚未執(zhí)行完,而該線程持有Handler的引用(不然它怎么發(fā)消息給Handler?),這個(gè)Handler又持有Activity的引用,就導(dǎo)致該Activity無(wú)法被回收(即內(nèi)存泄露),直到網(wǎng)絡(luò)請(qǐng)求結(jié)束。
另外,如果執(zhí)行了Handler的postDelayed()方法,那么在設(shè)定的delay到達(dá)之前,會(huì)有一條MessageQueue -> Message -> Handler -> Activity的鏈,導(dǎo)致你的Activity被持有引用而無(wú)法被回收。
解決方法之一,使用弱引用:
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
以上就是對(duì)Android handler 消息機(jī)制的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料,謝謝大家對(duì)本站的支持!
- Android 優(yōu)化Handler防止內(nèi)存泄露
- 解決Android使用Handler造成內(nèi)存泄露問(wèn)題
- 使用Android Studio檢測(cè)內(nèi)存泄露(LeakCanary)
- 避免 Android中Context引起的內(nèi)存泄露
- Android 中Handler引起的內(nèi)存泄露
- Android垃圾回收機(jī)制解決內(nèi)存泄露問(wèn)題
- Android中Handler引起的內(nèi)存泄露問(wèn)題解決辦法
- Android編程中避免內(nèi)存泄露的方法總結(jié)
- Android App調(diào)試內(nèi)存泄露之Cursor篇
- 分析Android常見(jiàn)的內(nèi)存泄露和解決方案
相關(guān)文章
Android中ViewPager帶來(lái)的滑動(dòng)卡頓問(wèn)題解決要點(diǎn)解析
這里我們主要針對(duì)ViewGroup的SwipeRefreshLayout中引入ViewPager所引起的滑動(dòng)沖突問(wèn)題進(jìn)行討論,一起來(lái)看一下Android中ViewPager帶來(lái)的滑動(dòng)卡頓問(wèn)題解決要點(diǎn)解析:2016-06-06Kotlin基礎(chǔ)學(xué)習(xí)之lambda中return語(yǔ)句詳解
這篇文章主要給大家介紹了關(guān)于Kotlin基礎(chǔ)學(xué)習(xí)之lambda中return語(yǔ)句的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或使用Kotlin具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07詳解Android實(shí)現(xiàn)定時(shí)器的幾種方法
本篇文章主要介紹了詳解Android實(shí)現(xiàn)定時(shí)器的幾種方法,主要包括了Handler, Timer, Thread, AlarmManager,有興趣的可以了解一下2017-09-09android效果TapBarMenu繪制底部導(dǎo)航欄的使用方式示例
本篇文章主要介紹了android效果TapBarMenu繪制底部導(dǎo)航欄的使用方式,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01Android實(shí)現(xiàn)快遞單號(hào)查詢快遞狀態(tài)信息
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)快遞單號(hào)查詢快遞狀態(tài)信息,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Android實(shí)現(xiàn)網(wǎng)頁(yè)圖片瀏覽功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)網(wǎng)頁(yè)圖片瀏覽功能,輸入圖片的url然后點(diǎn)擊按鈕加載出來(lái)圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Android混合開(kāi)發(fā)教程之WebView的使用方法總結(jié)
WebView是一個(gè)基于webkit引擎、展現(xiàn)web頁(yè)面的控件,下面這篇文章主要給大家介紹了關(guān)于Android混合開(kāi)發(fā)教程之WebView的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧2018-05-05Android實(shí)現(xiàn)簡(jiǎn)單下拉篩選框
這篇文章主要為大家詳細(xì)介紹了一款簡(jiǎn)單靈活的Android下拉篩選框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10