Android編程實現(xiàn)異步消息處理機(jī)制的幾種方法總結(jié)
本文實例講述了Android編程實現(xiàn)異步消息處理機(jī)制的幾種方法。分享給大家供大家參考,具體如下:
1、概述
Android需要更新ui的話就必須在ui線程上進(jìn)行操作。否則就會拋異常。
假如有耗時操作,比如:在子線程中下載文件,通知ui線程下載進(jìn)度,ui線程去更新進(jìn)度等,這個時候我們就需要用到異步消息處理。
一、什么是Handler
Handler是Android提供用來異步更新UI的一套機(jī)制,也是一套消息處理機(jī)制,可以用它來發(fā)送消息,也可以用它來接收消息。
二、為什么使用Handler
Android在設(shè)計之時,就封裝了一套消息的創(chuàng)建、傳遞、處理機(jī)制,作為系統(tǒng)原生的異步消息處理機(jī)制的實現(xiàn)之一,我們需要遵循這樣的處理機(jī)制,該機(jī)制的另外一種實現(xiàn)是AsyncTask。
三、Handler用法
1、postdelayed()
延時發(fā)送執(zhí)行子線程(Demo)
2、sendMessage()
回調(diào)handleMessage()
傳遞消息
3、sendToTarget()
傳遞消息
四、為什么在Android中只能通過Handler機(jī)制在主線程中更新UI?
最根本的是解決多線程并發(fā)問題。
假如在同一個Activity中,有多個線程同時更新UI,且沒有加鎖,那會導(dǎo)致什么問題呢?
UI更新混亂。
假如加鎖呢?
會導(dǎo)致性能下降。
使用Handler機(jī)制,我們不用去考慮多線程的問題,所有更新UI的操作,都是在 主線程消息隊列中輪詢?nèi)ヌ幚淼摹?br />
Handler 、 Looper 、Message 這三者都與Android異步消息處理線程相關(guān)的概念。那么什么叫異步消息處理線程呢?
異步消息處理線程啟動后會進(jìn)入一個無限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊列中取出一個消息,然后回調(diào)相應(yīng)的消息處理函數(shù),執(zhí)行完成一個消息后則繼續(xù)循環(huán)。若消息隊列為空,線程則會阻塞等待。
—此處有圖為證。
源碼解析
1、Looper
對于Looper主要是prepare()
和loop()
兩個方法。
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是一個ThreadLocal對象,可以在一個線程中存儲變量。在第5行,將一個Looper的實例放入了ThreadLocal,并且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說明了Looper.prepare()
方法不能被調(diào)用兩次,同時也保證了一個線程中只有一個Looper實例~
B. Looper的構(gòu)造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在構(gòu)造方法中,創(chuàng)建了一個MessageQueue(消息隊列)。
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存儲的Looper實例,如果me為null則拋出異常,也就是說loop方法必須在prepare方法之后執(zhí)行。
第6行:拿到該looper實例中的mQueue(消息隊列)
13到45行:就進(jìn)入了我們所說的無限循環(huán)。
14行:取出一條消息,如果沒有消息則阻塞。
27行:使用調(diào)用 msg.target.dispatchMessage(msg);
把消息交給msg的target的dispatchMessage
方法去處理。Msg的target是什么呢?其實就是handler對象,下面會進(jìn)行分析。
44行:釋放消息占據(jù)的資源。
Looper主要作用:
1、 與當(dāng)前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue。
2、 loop()
方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。
好了,我們的異步消息處理線程已經(jīng)有了消息隊列(MessageQueue),也有了在無限循環(huán)體中取出消息的哥們,現(xiàn)在缺的就是發(fā)送消息的對象了,于是乎:Handler登場了。
2、Handler
使用Handler之前,我們都是初始化一個實例,比如用于更新UI線程,我們會在聲明的時候直接初始化,或者在onCreate中初始化Handler實例。所以我們首先看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行:通過Looper.myLooper()
獲取了當(dāng)前線程保存的Looper實例,然后在19行又獲取了這個Looper實例中保存的MessageQueue(消息隊列)
,這樣就保證了handler的實例與我們Looper實例中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方法會取出每個msg然后交給msg,target.dispatchMessage(msg)去處理消息】,也就是把當(dāng)前的handler作為msg的target屬性。最終會調(diào)用queue的enqueueMessage的方法,也就是說handler發(fā)出的消息,最終會保存到消息隊列中去。
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)タ催@個方法:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
可以看到這是一個空方法,為什么呢,因為消息的最終回調(diào)是由我們控制的,我們在創(chuàng)建handler的時候都是復(fù)寫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中,得到了一個Message對象,然后將我們創(chuàng)建的Runable對象作為callback屬性,賦值給了此message.
注:產(chǎn)生一個Message對象,可以new
,也可以使用Message.obtain()
方法;兩者都可以,但是更建議使用obtain方法,因為Message內(nèi)部維護(hù)了一個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都有值,那么會執(zhí)行哪個呢?
看dispatchMessage方法就能看出來。
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對象。
mCallback 的值是如何賦值的,可以查看Handler的構(gòu)造方法,默認(rèn)mCallback 的值為Null
到此,這個流程已經(jīng)解釋完畢,總結(jié)一下
- 1、首先
Looper.prepare()
在本線程中保存一個Looper實例,然后該實例中保存一個MessageQueue對象;因為Looper.prepare()
在一個線程中只能調(diào)用一次,所以MessageQueue在一個線程中只會存在一個。 - 2、
Looper.loop()
會讓當(dāng)前線程進(jìn)入一個無限循環(huán),不斷從MessageQueue的實例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)
方法。 - 3、Handler的構(gòu)造方法,會首先得到當(dāng)前線程中保存的Looper實例,進(jìn)而與Looper實例中的MessageQueue相關(guān)聯(lián)。
- 4、Handler的sendMessage方法,會給msg的target賦值為handler自身,然后加入MessageQueue中。
- 5、在構(gòu)造Handler實例時,我們會重寫handleMessage方法,也就是
msg.target.dispatchMessage(msg)
最終調(diào)用的方法。
在Activity中,我們并沒有顯示的調(diào)用Looper.prepare()
和Looper.loop()
方法,為啥Handler可以成功創(chuàng)建呢,這是因為在Activity的啟動代碼中,已經(jīng)在當(dāng)前UI線程調(diào)用了Looper.prepare()
和Looper.loop()
方法。
4、擴(kuò)展
其實Handler不僅可以更新UI,你完全可以在一個子線程中去創(chuàng)建一個Handler,然后使用這個handler實例在任何其他線程中發(fā)送消息,最終處理消息的代碼都會在你創(chuàng)建Handler實例的線程中運(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ī)制的另一種實現(xiàn):AsyncTask:
主要方法:
onPreExecute()
: 這個方法是在執(zhí)行異步任務(wù)之前的時候執(zhí)行,并且是在UI
Thread當(dāng)中執(zhí)行的,通常我們在這個方法里做一些UI控件的初始化的操作,例如彈出ProgressDialogdoInBackground(Params… params)
:在
onPreExecute()
方法執(zhí)行完后,會馬上執(zhí)行這個方法,這個方法就是來處理異步任務(wù)的方法,Android操作系統(tǒng)會在后臺的線程池當(dāng)中開啟一個worker
thread來執(zhí)行這個方法(即在worker thread當(dāng)中執(zhí)行),執(zhí)行完后將執(zhí)行結(jié)果發(fā)送給最后一個 onPostExecute
方法,在這個方法里,我們可以從網(wǎng)絡(luò)當(dāng)中獲取數(shù)據(jù)等一些耗時的操作onProgressUpdate(Progess… values)
: 這個方法也是在UIThread當(dāng)中執(zhí)行的,在異步任務(wù)執(zhí)行的時候,有時需要將執(zhí)行的進(jìn)度返回給UI界面,例如下載一張網(wǎng)絡(luò)圖片,我們需要時刻顯示其下載的進(jìn)度,就可以使用這個方法來更新進(jìn)度。這個方法在調(diào)用之前,我們需要在
doInBackground 方法中調(diào)用一個 publishProgress(Progress) 的方法來將進(jìn)度時時刻刻傳遞給
onProgressUpdate 方法來更新
onPostExecute(Result… result)
: 當(dāng)異步任務(wù)執(zhí)行完之后,就會將結(jié)果返回給這個方法,這個方法也是在UIThread當(dāng)中調(diào)用的,我們可以將返回的結(jié)果顯示在UI控件上
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android線程與消息機(jī)制用法總結(jié)》、《Android開發(fā)入門與進(jìn)階教程》、《Android調(diào)試技巧與常見問題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對大家Android程序設(shè)計有所幫助。
相關(guān)文章
Android編程開發(fā)之Spinner控件用法實例分析
這篇文章主要介紹了Android編程開發(fā)之Spinner控件用法,結(jié)合實例形式較為詳細(xì)的分析了下拉列表Spinner的具體使用技巧,需要的朋友可以參考下2015-12-12將替代ListView的RecyclerView 的使用詳解(一)
這篇文章主要介紹了將替代ListView的RecyclerView 的使用詳解(一)的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07Android ContentProvider實現(xiàn)獲取手機(jī)聯(lián)系人功能
這篇文章主要為大家詳細(xì)介紹了Android ContentProvider實現(xiàn)獲取手機(jī)聯(lián)系人功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android中判斷手機(jī)是否聯(lián)網(wǎng)實例
這篇文章主要介紹了Android中判斷手機(jī)是否聯(lián)網(wǎng)實例,包括xml配置文件及功能代碼的實現(xiàn),需要的朋友可以參考下2014-10-10解決Android studio用真機(jī)調(diào)試時logcat一直輸出日志問題
這篇文章主要介紹了解決Android studio用真機(jī)調(diào)試時logcat一直輸出日志問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04Android使用Retrofit2.0技術(shù)仿微信發(fā)說說
這篇文章主要為大家詳細(xì)介紹了Android使用Retrofit2.0技術(shù)仿微信發(fā)說說,實現(xiàn)拍照,選圖庫,多圖案上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01