中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

Android中Handler機(jī)制的工作原理是什么-創(chuàng)新互聯(lián)

本篇文章為大家展示了Android中Handler機(jī)制的工作原理是什么,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

在高密等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作按需規(guī)劃網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),營銷型網(wǎng)站建設(shè),成都外貿(mào)網(wǎng)站建設(shè)公司,高密網(wǎng)站建設(shè)費(fèi)用合理。

Looper

在使用Handler之前,我們必須得初始化Looper,并讓Looper跑起來。

Looper.prepare();
...
Looper.loop();

執(zhí)行上面兩條語句之后,Looper就可以跑起來了。先來看看對應(yīng)的源碼:

public static void prepare() {
 prepare(true);
}

private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {
 throw new RuntimeException("Only one Looper may be created per thread");
 }
 sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed);
 mThread = Thread.currentThread();
}

必須保證一個線程中有且只有一個Looper對象,所以在初始化Looper的時候,會檢查當(dāng)前線程有沒有Looper對象。Looper的初始化會創(chuàng)建一個MessageQueue。創(chuàng)建完Looper后會放到ThreadLocal中去,關(guān)于ThreadLocal,后面會說到。

public static void loop() {
 // 判斷當(dāng)前線程有沒有初始化Looper
 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;

 ...

 for (;;) {
 Message msg = queue.next(); // might block
 if (msg == null) {
 // No message indicates that the message queue is quitting.
 return;
 }
 
 ...

 final long traceTag = me.mTraceTag;

 if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
 }

 try {
 // target指的是Handler
 msg.target.dispatchMessage(msg);
 } finally {
 if (traceTag != 0) {
 Trace.traceEnd(traceTag);
 }
 }
 
 ...

 msg.recycleUnchecked();
 }
}

方法比較長,所以只把最核心的代碼放了出來。省略掉的代碼中有一個比較有意思的:我們可以指定一個閾值比如說200,當(dāng)Message的處理超過200ms時,就會輸出Log。這可以在開發(fā)中幫助我們發(fā)現(xiàn)一些潛在的性能問題??上У氖?,設(shè)置閾值的方法是隱藏的,無法直接調(diào)用,所以這里就不放出代碼了,感興趣的朋友自己翻一下源碼吧。

簡化后的代碼可以看出邏輯十分簡單,可以說Looper在當(dāng)中扮演著搬磚工的角色,從MessageQueue中取出Message,然后交給Handler去分發(fā),再去MessageQueue中取出Message...無窮無盡,就像愚公移山一樣。

看到這里,應(yīng)該多多少少會覺得有點(diǎn)不對勁,因?yàn)檫@里是一個死循環(huán),按道理來說會一直占著CPU資源的,并且消息也總有處理完的時候,難道處理完就從消息隊(duì)列返回Null,然后Looper結(jié)束嗎?顯然不是,注意看注釋might block。

MessageQueue

答案就在MessageQueue里面,直接來看一下next():

Message next() {
 ...
 int pendingIdleHandlerCount = -1; // -1 only during first iteration
 int nextPollTimeoutMillis = 0;
 for (;;) {
 if (nextPollTimeoutMillis != 0) {
 Binder.flushPendingCommands();
 }

 nativePollOnce(ptr, nextPollTimeoutMillis);

 synchronized (this) {
 // Try to retrieve the next message. Return if found.
 final long now = SystemClock.uptimeMillis();
 Message prevMsg = null;
 Message msg = mMessages;
 if (msg != null && msg.target == null) {
 // Stalled by a barrier. Find the next asynchronous message in the queue.
 do {
 prevMsg = msg;
 msg = msg.next;
 } while (msg != null && !msg.isAsynchronous());
 }
 if (msg != null) {
 if (now < msg.when) {
 // Next message is not ready. Set a timeout to wake up when it is ready.
 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
 } else {
 // Got a message.
 mBlocked = false;
 if (prevMsg != null) {
 prevMsg.next = msg.next;
 } else {
 mMessages = msg.next;
 }
 msg.next = null;
 if (DEBUG) Log.v(TAG, "Returning message: " + msg);
 msg.markInUse();
 return msg;
 }
 } else {
 // No more messages.
 nextPollTimeoutMillis = -1;
 }

 // Process the quit message now that all pending messages have been handled.
 if (mQuitting) {
 dispose();
 return null;
 }

 // If first time idle, then get the number of idlers to run.
 // Idle handles only run if the queue is empty or if the first message
 // in the queue (possibly a barrier) is due to be handled in the future.
 if (pendingIdleHandlerCount < 0
 && (mMessages == null || now < mMessages.when)) {
 pendingIdleHandlerCount = mIdleHandlers.size();
 }
 if (pendingIdleHandlerCount <= 0) {
 // No idle handlers to run. Loop and wait some more.
 mBlocked = true;
 continue;
 }

 if (mPendingIdleHandlers == null) {
 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
 }
 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
 }

 // Run the idle handlers.
 // We only ever reach this code block during the first iteration.
 for (int i = 0; i < pendingIdleHandlerCount; i++) {
 final IdleHandler idler = mPendingIdleHandlers[i];
 mPendingIdleHandlers[i] = null; // release the reference to the handler

 boolean keep = false;
 try {
 keep = idler.queueIdle();
 } catch (Throwable t) {
 Log.wtf(TAG, "IdleHandler threw exception", t);
 }

 if (!keep) {
 synchronized (this) {
 mIdleHandlers.remove(idler);
 }
 }
 }

 // Reset the idle handler count to 0 so we do not run them again.
 pendingIdleHandlerCount = 0;

 // While calling an idle handler, a new message could have been delivered
 // so go back and look again for a pending message without waiting.
 nextPollTimeoutMillis = 0;
 }
}

代碼有點(diǎn)長,這次不打算省略掉一些了,因?yàn)檫@里面還有一個小彩蛋。

方法中最重要的應(yīng)該就是這一行了

nativePollOnce(ptr, nextPollTimeoutMillis);

簡單來說,當(dāng)nextPollTimeoutMillis == -1時,掛起當(dāng)前線程,釋放CPU資源,當(dāng)nextPollTimeoutMillis >= 0時會延時指定的時間激活一次線程,讓代碼繼續(xù)執(zhí)行下去。這里涉及到了底層的pipe管道和epoll機(jī)制,就不再講下去了(其實(shí)是因?yàn)橹v不下去了)。這也就可以回答上面的問題了,當(dāng)沒有消息的時候只需要讓線程掛起就行了,這樣可以保證不占用CPU資源的同時保住Looper的死循環(huán)。

然后我們來看消息是如何取出來的。MessageQueue中有一個Message,Message類中又有一個Message成員next,可以看出Message是一個單鏈表結(jié)構(gòu)。消息的順序是根據(jù)時間先后順序排列的。一般來說,我們要取的Message就是第一個(這里先不考慮異步消息,關(guān)于異步消息以后會講到的,又成功給自己挖了一個坑哈哈),如果當(dāng)前時間大于等于Message中指定的時間,那么將消息取出來,返回給Looper。由于此時nextPollTimeoutMillis的值為0,所以當(dāng)前面的消息處理完之后,Looper就又來取消息了。

如果當(dāng)前的時間小于Message中指定的時間,那么設(shè)置nextPollTimeoutMillis值以便下次喚醒。還有另外一種當(dāng)前已經(jīng)沒有消息了,nextPollTimeoutMillis會被設(shè)置為-1,也就是掛起線程。別急,還沒那么快呢,接著往下看。

緊接著的邏輯是判斷當(dāng)前有沒有IdleHandler,沒有的話就continue,該掛起就掛起,該延時就延時,有IdleHandler的話會執(zhí)行它的queueIdle()方法。這個IdleHandler是干什么的呢?從名字應(yīng)該也能猜出個一二來,這里就不再展開講了。關(guān)于它的一些妙用可以看我之前寫的Android 啟動優(yōu)化之延時加載。執(zhí)行完queueIdle()方法后,會將nextPollTimeoutMillis置為0,重新看一下消息隊(duì)列中有沒有新的消息。

Handler

上面將取消息的流程都講清楚了,萬事俱備,就差往消息隊(duì)列中添加消息了,該我們最熟悉的Handler出場了。Handler往隊(duì)列中添加消息,主要有兩種方式:

Handler.sendXXX();
Handler.postXXX();

第一種主要是發(fā)送Message,第二種是Runnable。無論是哪種方式,最終都會進(jìn)入到MessageQueue的enqueueMessage()方法。

boolean enqueueMessage(Message msg, long when) {
 ...

 synchronized (this) {
 ...

 msg.markInUse();
 msg.when = when;
 Message p = mMessages;
 boolean needWake;
 if (p == null || when == 0 || when < p.when) {
 // New head, wake up the event queue if blocked.
 msg.next = p;
 mMessages = msg;
 needWake = mBlocked;
 } else {
 // Inserted within the middle of the queue. Usually we don't have to wake
 // up the event queue unless there is a barrier at the head of the queue
 // and the message is the earliest asynchronous message in the queue.
 needWake = mBlocked && p.target == null && msg.isAsynchronous();
 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;
 }

 // We can assume mPtr != 0 because mQuitting is false.
 if (needWake) {
 nativeWake(mPtr);
 }
 }
 return true;
}

一般情況下,我們通過Handler發(fā)送消息的時候,會通過SystemClock.uptimeMillis()獲取一個開機(jī)時間,然后MessageQueue就會根據(jù)這個時間來對Message進(jìn)行排序。所以enqueueMessage()方法中就分了兩種情況,一種是直接可以在隊(duì)頭插入的。一種是排在中間,需要遍歷一下,然后尋一個合適的坑插入。when == 0對應(yīng)的是Handler的sendMessageAtFrontOfQueue()和postAtFrontOfQueue()方法。needWake的作用是根據(jù)情況喚醒Looper線程。

上面有一點(diǎn)還沒有講,就是Looper從MessageQueue中取出Message后,會交由Handler進(jìn)行消息的分發(fā)。

public void dispatchMessage(Message msg) {
 if (msg.callback != null) {
 handleCallback(msg);
 } else {
 if (mCallback != null) {
 if (mCallback.handleMessage(msg)) {
 return;
 }
 }
 handleMessage(msg);
 }
}

優(yōu)先級順序是Message自帶的callback,接著是Handler自帶的callback,最后才是handleMessage()這個回調(diào)。

ThreadLocal

還記得Looper中有一個ThreadLocal吧,把它放到最后來講是因?yàn)樗梢詥为?dú)拿出來講,不想在上面干擾到整個流程。

ThreadLocal是一個數(shù)據(jù)存儲類,它最神奇的地方就是明明是同一個ThreadLocal對象,但是在不同線程中可以存儲不同的對象,比如說在線程A中存儲了"Hello",而在線程B中存儲了"World"。它們之間互相不干擾。

在Handler機(jī)制中,由于一個Looper對應(yīng)著一個線程,所以將Looper存進(jìn)ThreadLocal最合適不過了。

ThreadLocal比價常用的就set()和get()方法。分別來看看怎么實(shí)現(xiàn)的吧。

public void set(T value) {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null)
 map.set(this, value);
 else
 createMap(t, value);
}

首先是去獲取ThreadLocalMap,找得到的話直接設(shè)置值,找不到就創(chuàng)建一個。

ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
}

看到這里,大概也能明白了。每個線程Thread中有一個ThreadLocalMap對象。通過ThreadLocal.set()方法,實(shí)際上是去獲取當(dāng)前線程中的ThreadLocalMap,線程不同,獲取到的ThreadLocalMap自然也不同。
再來看看這個ThreadLocalMap是什么來頭??搭惖淖⑨屩杏羞@么一句話:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.

從注釋中可以知道這就是一個定制的HashMap,并且它的Entry類指定了Key只能為ThreadLocal類型的。所以直接將它看成是一個HashMap就好了。

get()方法也好理解,就是從Map中取出值而已。大概看一下就好了。

public T get() {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null) {
 ThreadLocalMap.Entry e = map.getEntry(this);
 if (e != null) {
 @SuppressWarnings("unchecked")
 T result = (T)e.value;
 return result;
 }
 }
 return setInitialValue();
}

上述內(nèi)容就是Android中Handler機(jī)制的工作原理是什么,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)頁標(biāo)題:Android中Handler機(jī)制的工作原理是什么-創(chuàng)新互聯(lián)
文章出自:http://www.rwnh.cn/article46/jdghg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、Google虛擬主機(jī)、電子商務(wù)、云服務(wù)器、定制開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司
文成县| 时尚| 卓尼县| 昭苏县| 汤阴县| 台山市| 瓮安县| 中宁县| 张家港市| 江津市| 明水县| 通榆县| 南汇区| 临漳县| 定兴县| 全南县| 文登市| 卢湾区| 吴忠市| 丰县| 石屏县| 西乌珠穆沁旗| 公主岭市| 清镇市| 长宁区| 彭水| 镇平县| 遵化市| 和田县| 平利县| 年辖:市辖区| 濉溪县| 定结县| 滨海县| 北票市| 日土县| 大化| 浦东新区| 志丹县| 宜君县| 平罗县|