摘要:在子線程中發(fā)送消息,主線程接受到消息并且處理邏輯。也稱之為消息隊列,特點是先進(jìn)先出,底層實現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)得出結(jié)論方法初始話了一個對象并關(guān)聯(lián)在一個對象,并且一個線程中只有一個對象,只有一個對象。
目錄介紹
1.Handler的常見的使用方式
2.如何在子線程中定義Handler
3.主線程如何自動調(diào)用Looper.prepare()
4.Looper.prepare()方法源碼分析
5.Looper中用什么存儲消息
6.Handler發(fā)送消息如何運作
7.Looper.loop()方法源碼分析
8.runOnUiThread如何實現(xiàn)子線程更新UI
9.Handler的post方法和view的post方法
10.得出部分結(jié)論
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
00.Android異步消息機(jī)制
如何在子線程中定義Handler,主線程如何自動調(diào)用Looper.prepare(),Looper.prepare()方法源碼分析,Looper中用什么存儲消息,Looper.loop()方法源碼分析,runOnUiThread如何實現(xiàn)子線程更新UI等等
01.Handler消息機(jī)制
為什么不允許在子線程中訪問UI,Handler消息機(jī)制作用,避免子線程手動創(chuàng)建looper,ActivityThread源碼分析,ActivityThread源碼分析,Looper死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死,會消耗大量資源嗎?
1.Handler的常見的使用方式
handler機(jī)制大家都比較熟悉呢。在子線程中發(fā)送消息,主線程接受到消息并且處理邏輯。如下所示
一般handler的使用方式都是在主線程中定義Handler,然后在子線程中調(diào)用mHandler.sendXx()方法,這里有一個疑問可以在子線程中定義Handler嗎?
public class MainActivity extends AppCompatActivity { private TextView tv ; /** * 在主線程中定義Handler,并實現(xiàn)對應(yīng)的handleMessage方法 */ public static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 101) { Log.i("MainActivity", "接收到handler消息..."); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.tv); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { // 在子線程中發(fā)送異步消息 mHandler.sendEmptyMessage(1); } }.start(); } }); } }2.如何在子線程中定義Handler
直接在子線程中創(chuàng)建handler,看看會出現(xiàn)什么情況?
運行后可以得出在子線程中定義Handler對象出錯,難道Handler對象的定義或者是初始化只能在主線程中?其實不是這樣的,錯誤信息中提示的已經(jīng)很明顯了,在初始化Handler對象之前需要調(diào)用Looper.prepare()方法
tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { Log.i(TAG, "在子線程中定義Handler,接收并處理消息"); } } }; } }.start(); } });
如何正確運行。在這里問一個問題,在子線程中可以吐司嗎?答案是可以的,只不過又條件,詳細(xì)可以看這篇文章02.Toast源碼深度分析
這樣程序已經(jīng)不會報錯,那么這說明初始化Handler對象的時候我們是需要調(diào)用Looper.prepare()的,那么主線程中為什么可以直接初始化Handler呢?難道是主線程創(chuàng)建handler對象的時候,會自動調(diào)用Looper.prepare()方法的嗎?
tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { Looper.prepare(); Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { Log.i(TAG, "在子線程中定義Handler,接收并處理消息"); } } }; Looper.loop(); } }.start(); } });3.主線程如何自動調(diào)用Looper.prepare()
首先直接可以看在App初始化的時候會執(zhí)行ActivityThread的main方法中的代碼,如下所示
可以看到Looper.prepare()方法在這里調(diào)用,所以在主線程中可以直接初始化Handler了。
public static void main(String[] args) { //省略部分代碼 Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
并且可以看到還調(diào)用了:Looper.loop()方法,可以知道一個Handler的標(biāo)準(zhǔn)寫法其實是這樣的
Looper.prepare(); Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 101) { Log.i(TAG, "在子線程中定義Handler,并接收到消息"); } } }; Looper.loop();4.Looper.prepare()方法源碼分析
源碼如下所示
可以看到Looper中有一個ThreadLocal成員變量,熟悉JDK的同學(xué)應(yīng)該知道,當(dāng)使用ThreadLocal維護(hù)變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(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)); }
思考:Looper.prepare()能否調(diào)用兩次或者多次
如果運行,則會報錯,并提示prepare中的Excetion信息。由此可以得出在每個線程中Looper.prepare()能且只能調(diào)用一次
//這里L(fēng)ooper.prepare()方法調(diào)用了兩次 Looper.prepare(); Looper.prepare(); Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { Log.i(TAG, "在子線程中定義Handler,并接收到消息。。。"); } } }; Looper.loop();5.Looper中用什么存儲消息
先看一下下面得源代碼
看Looper對象的構(gòu)造方法,可以看到在其構(gòu)造方法中初始化了一個MessageQueue對象。MessageQueue也稱之為消息隊列,特點是先進(jìn)先出,底層實現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)
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(); }
得出結(jié)論
Looper.prepare()方法初始話了一個Looper對象并關(guān)聯(lián)在一個MessageQueue對象,并且一個線程中只有一個Looper對象,只有一個MessageQueue對象。
6.Handler發(fā)送消息如何運作
首先看看構(gòu)造方法
可以看出在Handler的構(gòu)造方法中,主要初始化了一下變量,并判斷Handler對象的初始化不應(yīng)再內(nèi)部類,靜態(tài)類,匿名類中,并且保存了當(dāng)前線程中的Looper對象。
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; }
看handler.sendMessage(msg)方法
關(guān)于下面得源碼,是步步追蹤,看enqueueMessage這個方法,原來msg.target就是Handler對象本身;而這里的queue對象就是我們的Handler內(nèi)部維護(hù)的Looper對象關(guān)聯(lián)的MessageQueue對象。
handler.sendMessage(message); //追蹤到這一步 public final boolean sendMessage(Message msg){ return sendMessageDelayed(msg, 0); } 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); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
看MessageQueue對象的enqueueMessage方法
看到這里MessageQueue并沒有使用列表將所有的Message保存起來,而是使用Message.next保存下一個Message,從而按照時間將所有的Message排序
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } 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; }7.Looper.loop()方法源碼分析
看看里面得源碼,如下所示
看到Looper.loop()方法里起了一個死循環(huán),不斷的判斷MessageQueue中的消息是否為空,如果為空則直接return掉,然后執(zhí)行queue.next()方法
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; 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 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, "Dispatch took " + time + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what); } } 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.recycleUnchecked(); } }
看queue.next()方法源碼
大概的實現(xiàn)邏輯就是Message的出棧操作,里面可能對線程,并發(fā)控制做了一些限制等。獲取到棧頂?shù)腗essage對象之后開始執(zhí)行:msg.target.dispatchMessage(msg)
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } 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; } }
那么msg.target是什么呢?通過追蹤可以知道就是定義的Handler對象,然后查看一下Handler類的dispatchMessage方法:
可以看到,如果我們設(shè)置了callback(Runnable對象)的話,則會直接調(diào)用handleCallback方法
在初始化Handler的時候設(shè)置了callback(Runnable)對象,則直接調(diào)用run方法。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }8.runOnUiThread如何實現(xiàn)子線程更新UI
看看源碼,如下所示
如果msg.callback為空的話,會直接調(diào)用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會在主線程中。
在runOnUiThread程序首先會判斷當(dāng)前線程是否是UI線程,如果是就直接運行,如果不是則post,這時其實質(zhì)還是使用的Handler機(jī)制來處理線程與UI通訊。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } @Override public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }9.Handler的post方法和view的post方法
Handler的post方法實現(xiàn)很簡單,如下所示
mHandler.post(new Runnable() { @Override public void run() { } }); public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }
view的post方法也很簡單,如下所示
可以發(fā)現(xiàn)其調(diào)用的就是activity中默認(rèn)保存的handler對象的post方法
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } ViewRootImpl.getRunQueue().post(action); return true; } public void post(Runnable action) { postDelayed(action, 0); } public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } }10.得出部分結(jié)論
得出得結(jié)論如下所示
1.主線程中定義Handler對象,ActivityThread的main方法中會自動創(chuàng)建一個looper,并且與其綁定。如果是子線程中直接創(chuàng)建handler對象,則需要手動創(chuàng)建looper。不過手動創(chuàng)建不太友好,需要手動調(diào)用quit方法結(jié)束looper。這個后面再說
2.一個線程中只存在一個Looper對象,只存在一個MessageQueue對象,可以存在N個Handler對象,Handler對象內(nèi)部關(guān)聯(lián)了本線程中唯一的Looper對象,Looper對象內(nèi)部關(guān)聯(lián)著唯一的一個MessageQueue對象。
3.MessageQueue消息隊列不是通過列表保存消息(Message)列表的,而是通過Message對象的next屬性關(guān)聯(lián)下一個Message從而實現(xiàn)列表的功能,同時所有的消息都是按時間排序的。
關(guān)于其他內(nèi)容介紹 01.關(guān)于博客匯總鏈接1.技術(shù)博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71907.html
摘要:在子線程中發(fā)送消息,主線程接受到消息并且處理邏輯。子線程往消息隊列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。 目錄介紹 1.Handler的常見的使用方式 2.如何在子線程中定義Handler 3.主線程如何自動調(diào)用Looper.prepare() 4.Looper.prepare()方法源碼分析 5....
閱讀 2013·2021-09-29 09:35
閱讀 1949·2019-08-30 14:15
閱讀 2973·2019-08-30 10:56
閱讀 955·2019-08-29 16:59
閱讀 571·2019-08-29 14:04
閱讀 1301·2019-08-29 12:30
閱讀 1020·2019-08-28 18:19
閱讀 509·2019-08-26 11:51