Android編程實(shí)現(xiàn)異步消息處理機(jī)制的幾種方法總結(jié)
本文實(shí)例講述了Android編程實(shí)現(xiàn)異步消息處理機(jī)制的幾種方法。分享給大家供大家參考,具體如下:
1、概述
Android需要更新ui的話就必須在ui線程上進(jìn)行操作。否則就會(huì)拋異常。
假如有耗時(shí)操作,比如:在子線程中下載文件,通知ui線程下載進(jìn)度,ui線程去更新進(jìn)度等,這個(gè)時(shí)候我們就需要用到異步消息處理。
一、什么是Handler
Handler是Android提供用來(lái)異步更新UI的一套機(jī)制,也是一套消息處理機(jī)制,可以用它來(lái)發(fā)送消息,也可以用它來(lái)接收消息。
二、為什么使用Handler
Android在設(shè)計(jì)之時(shí),就封裝了一套消息的創(chuàng)建、傳遞、處理機(jī)制,作為系統(tǒng)原生的異步消息處理機(jī)制的實(shí)現(xiàn)之一,我們需要遵循這樣的處理機(jī)制,該機(jī)制的另外一種實(shí)現(xiàn)是AsyncTask。
三、Handler用法
1、postdelayed()
延時(shí)發(fā)送執(zhí)行子線程(Demo)
2、sendMessage()
回調(diào)handleMessage()
傳遞消息
3、sendToTarget()
傳遞消息
四、為什么在Android中只能通過(guò)Handler機(jī)制在主線程中更新UI?
最根本的是解決多線程并發(fā)問(wèn)題。
假如在同一個(gè)Activity中,有多個(gè)線程同時(shí)更新UI,且沒(méi)有加鎖,那會(huì)導(dǎo)致什么問(wèn)題呢?
UI更新混亂。
假如加鎖呢?
會(huì)導(dǎo)致性能下降。
使用Handler機(jī)制,我們不用去考慮多線程的問(wèn)題,所有更新UI的操作,都是在 主線程消息隊(duì)列中輪詢?nèi)ヌ幚淼摹?br />
Handler 、 Looper 、Message 這三者都與Android異步消息處理線程相關(guān)的概念。那么什么叫異步消息處理線程呢?
異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無(wú)限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息處理函數(shù),執(zhí)行完成一個(gè)消息后則繼續(xù)循環(huán)。若消息隊(duì)列為空,線程則會(huì)阻塞等待。
—此處有圖為證。
源碼解析
1、Looper
對(duì)于Looper主要是prepare()
和loop()
兩個(gè)方法。
A. 首先看prepare()
方法
public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(true)); }
sThreadLocal是一個(gè)ThreadLocal對(duì)象,可以在一個(gè)線程中存儲(chǔ)變量。在第5行,將一個(gè)Looper的實(shí)例放入了ThreadLocal,并且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說(shuō)明了Looper.prepare()
方法不能被調(diào)用兩次,同時(shí)也保證了一個(gè)線程中只有一個(gè)Looper實(shí)例~
B. Looper的構(gòu)造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在構(gòu)造方法中,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。
C. 然后我們看loop()
方法
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } }
第2行:
public static Looper myLooper() { return sThreadLocal.get(); }
方法直接返回了sThreadLocal存儲(chǔ)的Looper實(shí)例,如果me為null則拋出異常,也就是說(shuō)loop方法必須在prepare方法之后執(zhí)行。
第6行:拿到該looper實(shí)例中的mQueue(消息隊(duì)列)
13到45行:就進(jìn)入了我們所說(shuō)的無(wú)限循環(huán)。
14行:取出一條消息,如果沒(méi)有消息則阻塞。
27行:使用調(diào)用 msg.target.dispatchMessage(msg);
把消息交給msg的target的dispatchMessage
方法去處理。Msg的target是什么呢?其實(shí)就是handler對(duì)象,下面會(huì)進(jìn)行分析。
44行:釋放消息占據(jù)的資源。
Looper主要作用:
1、 與當(dāng)前線程綁定,保證一個(gè)線程只會(huì)有一個(gè)Looper實(shí)例,同時(shí)一個(gè)Looper實(shí)例也只有一個(gè)MessageQueue。
2、 loop()
方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。
好了,我們的異步消息處理線程已經(jīng)有了消息隊(duì)列(MessageQueue),也有了在無(wú)限循環(huán)體中取出消息的哥們,現(xiàn)在缺的就是發(fā)送消息的對(duì)象了,于是乎:Handler登場(chǎng)了。
2、Handler
使用Handler之前,我們都是初始化一個(gè)實(shí)例,比如用于更新UI線程,我們會(huì)在聲明的時(shí)候直接初始化,或者在onCreate中初始化Handler實(shí)例。所以我們首先看Handler的構(gòu)造方法,看其如何與MessageQueue聯(lián)系上的,它在子線程中發(fā)送的消息(一般發(fā)送消息都在非UI線程)怎么發(fā)送到MessageQueue中的。
public Handler() { this(null, false); } 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; }
14行:通過(guò)Looper.myLooper()
獲取了當(dāng)前線程保存的Looper實(shí)例,然后在19行又獲取了這個(gè)Looper實(shí)例中保存的MessageQueue(消息隊(duì)列)
,這樣就保證了handler的實(shí)例與我們Looper實(shí)例中MessageQueue關(guān)聯(lián)上了。
A.sendMessage方法
輾轉(zhuǎn)反則最后調(diào)用了sendMessageAtTime方法。
B. enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
enqueueMessage中首先為msg.target賦值為this,【如果大家還記得Looper的loop方法會(huì)取出每個(gè)msg然后交給msg,target.dispatchMessage(msg)去處理消息】,也就是把當(dāng)前的handler作為msg的target屬性。最終會(huì)調(diào)用queue的enqueueMessage的方法,也就是說(shuō)handler發(fā)出的消息,最終會(huì)保存到消息隊(duì)列中去。
C. dispathMessage方法
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
可以看到,第10行,調(diào)用了handleMessage方法,下面我們?nèi)タ催@個(gè)方法:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
可以看到這是一個(gè)空方法,為什么呢,因?yàn)橄⒌淖罱K回調(diào)是由我們控制的,我們?cè)趧?chuàng)建handler的時(shí)候都是復(fù)寫(xiě)handleMessage
方法,然后根據(jù)msg.what
進(jìn)行消息處理。
3、Handler post
post方法:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
getPostMessage方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
可以看到,在getPostMessage中,得到了一個(gè)Message對(duì)象,然后將我們創(chuàng)建的Runable對(duì)象作為callback屬性,賦值給了此message.
注:產(chǎn)生一個(gè)Message對(duì)象,可以new
,也可以使用Message.obtain()
方法;兩者都可以,但是更建議使用obtain方法,因?yàn)镸essage內(nèi)部維護(hù)了一個(gè)Message池用于Message的復(fù)用,避免使用new
重新分配內(nèi)存。
sendMessageDelayed
方法和handler.sendMessage
方法最終調(diào)用的都是:
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); }
可以看到,這里msg的callback和target都有值,那么會(huì)執(zhí)行哪個(gè)呢?
看dispatchMessage方法就能看出來(lái)。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
第2行,如果不為null,則執(zhí)行callback回調(diào),也就是我們的Runnable對(duì)象。
mCallback 的值是如何賦值的,可以查看Handler的構(gòu)造方法,默認(rèn)mCallback 的值為Null
到此,這個(gè)流程已經(jīng)解釋完畢,總結(jié)一下
- 1、首先
Looper.prepare()
在本線程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象;因?yàn)?code>Looper.prepare()在一個(gè)線程中只能調(diào)用一次,所以MessageQueue在一個(gè)線程中只會(huì)存在一個(gè)。 - 2、
Looper.loop()
會(huì)讓當(dāng)前線程進(jìn)入一個(gè)無(wú)限循環(huán),不斷從MessageQueue的實(shí)例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)
方法。 - 3、Handler的構(gòu)造方法,會(huì)首先得到當(dāng)前線程中保存的Looper實(shí)例,進(jìn)而與Looper實(shí)例中的MessageQueue相關(guān)聯(lián)。
- 4、Handler的sendMessage方法,會(huì)給msg的target賦值為handler自身,然后加入MessageQueue中。
- 5、在構(gòu)造Handler實(shí)例時(shí),我們會(huì)重寫(xiě)handleMessage方法,也就是
msg.target.dispatchMessage(msg)
最終調(diào)用的方法。
在Activity中,我們并沒(méi)有顯示的調(diào)用Looper.prepare()
和Looper.loop()
方法,為啥Handler可以成功創(chuàng)建呢,這是因?yàn)樵贏ctivity的啟動(dòng)代碼中,已經(jīng)在當(dāng)前UI線程調(diào)用了Looper.prepare()
和Looper.loop()
方法。
4、擴(kuò)展
其實(shí)Handler不僅可以更新UI,你完全可以在一個(gè)子線程中去創(chuàng)建一個(gè)Handler,然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息,最終處理消息的代碼都會(huì)在你創(chuàng)建Handler實(shí)例的線程中運(yùn)行。
代碼:
new Thread() { private Handler handler; public void run() { Looper.prepare(); handler = new Handler() { public void handleMessage(android.os.Message msg) { Log.e("TAG",Thread.currentThread().getName()); }; }; Looper.loop(); }
四種更新UI的方法
1、Handler.post();
2、Handler.sendMessage();
3、runOnUIThread()
4、View.post()
查看runOnUIThread()的源代碼(Activity中)
Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.
Parameters:
action the action to run on the UI thread
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
補(bǔ)充:
1.異步消息處理機(jī)制的另一種實(shí)現(xiàn):AsyncTask:
主要方法:
onPreExecute()
: 這個(gè)方法是在執(zhí)行異步任務(wù)之前的時(shí)候執(zhí)行,并且是在UI
Thread當(dāng)中執(zhí)行的,通常我們?cè)谶@個(gè)方法里做一些UI控件的初始化的操作,例如彈出ProgressDialogdoInBackground(Params… params)
:在
onPreExecute()
方法執(zhí)行完后,會(huì)馬上執(zhí)行這個(gè)方法,這個(gè)方法就是來(lái)處理異步任務(wù)的方法,Android操作系統(tǒng)會(huì)在后臺(tái)的線程池當(dāng)中開(kāi)啟一個(gè)worker
thread來(lái)執(zhí)行這個(gè)方法(即在worker thread當(dāng)中執(zhí)行),執(zhí)行完后將執(zhí)行結(jié)果發(fā)送給最后一個(gè) onPostExecute
方法,在這個(gè)方法里,我們可以從網(wǎng)絡(luò)當(dāng)中獲取數(shù)據(jù)等一些耗時(shí)的操作onProgressUpdate(Progess… values)
: 這個(gè)方法也是在UIThread當(dāng)中執(zhí)行的,在異步任務(wù)執(zhí)行的時(shí)候,有時(shí)需要將執(zhí)行的進(jìn)度返回給UI界面,例如下載一張網(wǎng)絡(luò)圖片,我們需要時(shí)刻顯示其下載的進(jìn)度,就可以使用這個(gè)方法來(lái)更新進(jìn)度。這個(gè)方法在調(diào)用之前,我們需要在
doInBackground 方法中調(diào)用一個(gè) publishProgress(Progress) 的方法來(lái)將進(jìn)度時(shí)時(shí)刻刻傳遞給
onProgressUpdate 方法來(lái)更新
onPostExecute(Result… result)
: 當(dāng)異步任務(wù)執(zhí)行完之后,就會(huì)將結(jié)果返回給這個(gè)方法,這個(gè)方法也是在UIThread當(dāng)中調(diào)用的,我們可以將返回的結(jié)果顯示在UI控件上
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android線程與消息機(jī)制用法總結(jié)》、《Android開(kāi)發(fā)入門(mén)與進(jìn)階教程》、《Android調(diào)試技巧與常見(jiàn)問(wèn)題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。
相關(guān)文章
Android編程開(kāi)發(fā)之Spinner控件用法實(shí)例分析
這篇文章主要介紹了Android編程開(kāi)發(fā)之Spinner控件用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了下拉列表Spinner的具體使用技巧,需要的朋友可以參考下2015-12-12將替代ListView的RecyclerView 的使用詳解(一)
這篇文章主要介紹了將替代ListView的RecyclerView 的使用詳解(一)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07輕松實(shí)現(xiàn)Rxjava定時(shí)器功能
這篇文章主要為大家詳細(xì)介紹了Rxjava實(shí)現(xiàn)定時(shí)器功能的兩種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android ContentProvider實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能
這篇文章主要為大家詳細(xì)介紹了Android ContentProvider實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android簡(jiǎn)單實(shí)現(xiàn)畫(huà)圖功能
這篇文章主要為大家詳細(xì)介紹了Android簡(jiǎn)單實(shí)現(xiàn)畫(huà)圖功能的方法,以及實(shí)現(xiàn)過(guò)程中遇到的問(wèn)題,感興趣的小伙伴們可以參考一下2016-03-03Android中判斷手機(jī)是否聯(lián)網(wǎng)實(shí)例
這篇文章主要介紹了Android中判斷手機(jī)是否聯(lián)網(wǎng)實(shí)例,包括xml配置文件及功能代碼的實(shí)現(xiàn),需要的朋友可以參考下2014-10-10解決Android studio用真機(jī)調(diào)試時(shí)logcat一直輸出日志問(wèn)題
這篇文章主要介紹了解決Android studio用真機(jī)調(diào)試時(shí)logcat一直輸出日志問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04Android開(kāi)發(fā)之基本控件和四種布局方式詳解
這篇文章主要介紹了Android開(kāi)發(fā)之基本控件和四種布局方式詳解的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06Android使用Retrofit2.0技術(shù)仿微信發(fā)說(shuō)說(shuō)
這篇文章主要為大家詳細(xì)介紹了Android使用Retrofit2.0技術(shù)仿微信發(fā)說(shuō)說(shuō),實(shí)現(xiàn)拍照,選圖庫(kù),多圖案上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01