本文實(shí)例講述了Android編程實(shí)現(xiàn)異步消息處理機(jī)制的幾種方法。分享給大家供大家參考,具體如下:
Android需要更新ui的話就必須在ui線程上進(jìn)行操作。否則就會拋異常。
假如有耗時(shí)操作,比如:在子線程中下載文件,通知ui線程下載進(jìn)度,ui線程去更新進(jìn)度等,這個(gè)時(shí)候我們就需要用到異步消息處理。
Handler是Android提供用來異步更新UI的一套機(jī)制,也是一套消息處理機(jī)制,可以用它來發(fā)送消息,也可以用它來接收消息。
Android在設(shè)計(jì)之時(shí),就封裝了一套消息的創(chuàng)建、傳遞、處理機(jī)制,作為系統(tǒng)原生的異步消息處理機(jī)制的實(shí)現(xiàn)之一,我們需要遵循這樣的處理機(jī)制,該機(jī)制的另外一種實(shí)現(xiàn)是AsyncTask。
1、postdelayed()
延時(shí)發(fā)送執(zhí)行子線程(Demo)
2、sendMessage()
回調(diào)handleMessage()
傳遞消息
3、sendToTarget()
傳遞消息
最根本的是解決多線程并發(fā)問題。
假如在同一個(gè)Activity中,有多個(gè)線程同時(shí)更新UI,且沒有加鎖,那會導(dǎo)致什么問題呢?
UI更新混亂。
假如加鎖呢?
會導(dǎo)致性能下降。
使用Handler機(jī)制,我們不用去考慮多線程的問題,所有更新UI的操作,都是在 主線程消息隊(duì)列中輪詢?nèi)ヌ幚淼摹?br /> Handler 、 Looper 、Message 這三者都與Android異步消息處理線程相關(guān)的概念。那么什么叫異步消息處理線程呢?
異步消息處理線程啟動后會進(jìn)入一個(gè)無限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息處理函數(shù),執(zhí)行完成一個(gè)消息后則繼續(xù)循環(huán)。若消息隊(duì)列為空,線程則會阻塞等待。
—此處有圖為證。
對于Looper主要是prepare()
和loop()
兩個(gè)方法。
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對象,可以在一個(gè)線程中存儲變量。在第5行,將一個(gè)Looper的實(shí)例放入了ThreadLocal,并且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說明了Looper.prepare()
方法不能被調(diào)用兩次,同時(shí)也保證了一個(gè)線程中只有一個(gè)Looper實(shí)例~
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在構(gòu)造方法中,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。
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實(shí)例,如果me為null則拋出異常,也就是說loop方法必須在prepare方法之后執(zhí)行。
第6行:拿到該looper實(shí)例中的mQueue(消息隊(duì)列)
13到45行:就進(jìn)入了我們所說的無限循環(huán)。
14行:取出一條消息,如果沒有消息則阻塞。
27行:使用調(diào)用 msg.target.dispatchMessage(msg);
把消息交給msg的target的dispatchMessage
方法去處理。Msg的target是什么呢?其實(shí)就是handler對象,下面會進(jìn)行分析。
44行:釋放消息占據(jù)的資源。
Looper主要作用:
1、 與當(dāng)前線程綁定,保證一個(gè)線程只會有一個(gè)Looper實(shí)例,同時(shí)一個(gè)Looper實(shí)例也只有一個(gè)MessageQueue。
2、 loop()
方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。
好了,我們的異步消息處理線程已經(jīng)有了消息隊(duì)列(MessageQueue),也有了在無限循環(huán)體中取出消息的哥們,現(xiàn)在缺的就是發(fā)送消息的對象了,于是乎:Handler登場了。
使用Handler之前,我們都是初始化一個(gè)實(shí)例,比如用于更新UI線程,我們會在聲明的時(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行:通過Looper.myLooper()
獲取了當(dāng)前線程保存的Looper實(shí)例,然后在19行又獲取了這個(gè)Looper實(shí)例中保存的MessageQueue(消息隊(duì)列)
,這樣就保證了handler的實(shí)例與我們Looper實(shí)例中MessageQueue關(guān)聯(lián)上了。
輾轉(zhuǎn)反則最后調(diào)用了sendMessageAtTime方法。
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方法會取出每個(gè)msg然后交給msg,target.dispatchMessage(msg)去處理消息】,也就是把當(dāng)前的handler作為msg的target屬性。最終會調(diào)用queue的enqueueMessage的方法,也就是說handler發(fā)出的消息,最終會保存到消息隊(duì)列中去。
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)是由我們控制的,我們在創(chuàng)建handler的時(shí)候都是復(fù)寫handleMessage
方法,然后根據(jù)msg.what
進(jìn)行消息處理。
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對象,然后將我們創(chuàng)建的Runable對象作為callback屬性,賦值給了此message.
注:產(chǎn)生一個(gè)Message對象,可以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都有值,那么會執(zhí)行哪個(gè)呢?
看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
到此,這個(gè)流程已經(jīng)解釋完畢,總結(jié)一下
Looper.prepare()
在本線程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對象;因?yàn)?code style="margin: 3px auto 0px; padding: 2px 4px; outline: none; font-style: inherit; font-weight: inherit; background: rgb(249, 242, 244); width: 640px; line-height: 1.5; clear: both; font-size: 12px; border: 1px solid rgb(204, 204, 204); color: rgb(199, 37, 78); border-radius: 0px; font-family: Menlo, Monaco, Consolas, "Courier New", monospace;">Looper.prepare()在一個(gè)線程中只能調(diào)用一次,所以MessageQueue在一個(gè)線程中只會存在一個(gè)。Looper.loop()
會讓當(dāng)前線程進(jìn)入一個(gè)無限循環(huán),不斷從MessageQueue的實(shí)例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)
方法。msg.target.dispatchMessage(msg)
最終調(diào)用的方法。 在Activity中,我們并沒有顯示的調(diào)用Looper.prepare()
和Looper.loop()
方法,為啥Handler可以成功創(chuàng)建呢,這是因?yàn)樵贏ctivity的啟動代碼中,已經(jīng)在當(dāng)前UI線程調(diào)用了Looper.prepare()
和Looper.loop()
方法。
其實(shí)Handler不僅可以更新UI,你完全可以在一個(gè)子線程中去創(chuàng)建一個(gè)Handler,然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息,最終處理消息的代碼都會在你創(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í)行,并且是在UIdoInBackground(Params… params)
: 在onPreExecute()
方法執(zhí)行完后,會馬上執(zhí)行這個(gè)方法,這個(gè)方法就是來處理異步任務(wù)的方法,Android操作系統(tǒng)會在后臺的線程池當(dāng)中開啟一個(gè)worker
thread來執(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è)方法也是在UI Thread當(dāng)中執(zhí)行的,在異步任務(wù)執(zhí)行的時(shí)候,有時(shí)需要將執(zhí)行的進(jìn)度返回給UI界面,例如下載一張網(wǎng)絡(luò)圖片,我們需要時(shí)刻顯示其下載的進(jìn)度,就可以使用這個(gè)方法來更新進(jìn)度。這個(gè)方法在調(diào)用之前,我們需要在
doInBackground 方法中調(diào)用一個(gè) publishProgress(Progress) 的方法來將進(jìn)度時(shí)時(shí)刻刻傳遞給
onProgressUpdate 方法來更新
onPostExecute(Result… result)
: 當(dāng)異步任務(wù)執(zhí)行完之后,就會將結(jié)果返回給這個(gè)方法,這個(gè)方法也是在UI Thread當(dāng)中調(diào)用的,我們可以將返回的結(jié)果顯示在UI控件上
希望本文所述對大家Android程序設(shè)計(jì)有所幫助。
新聞熱點(diǎn)
疑難解答