博客主頁java
在分析Handler源碼以前,咱們先來看下下面這條異常android
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824) at android.view.View.requestLayout(View.java:16431)
做爲Android開發人員,這樣異常的信息應該並不陌生,產生的緣由就是在子線程操做UI控件了。那麼爲何在子線程操做UI控件,就會拋出異常呢?segmentfault
咱們再來看另外一條異常信息安全
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare() at android.widget.Toast$TN.<init>(Toast.java:435) at android.widget.Toast.<init>(Toast.java:150) at android.widget.Toast.makeText(Toast.java:313) at android.widget.Toast.makeText(Toast.java:303)
拋出這條異常信息是由於在子線程中彈toast致使的。爲何跟第一條產生的異常信息不同呢?多線程
讓咱們帶着疑問開始探索源碼之旅吧~~~併發
一、其實第一條異常信息咱們都知道是在哪裏拋出的,跟View的繪製機制有關,也就是爲何不容許在子線程中操做UI控件?
這是由於Android的UI控件不是線程安全的,若是在多線程中併發訪問可能會致使UI控件不可預期的狀態,那麼爲何系統不對UI控件的訪問加上鎖機制呢?app
因此最簡單且高效的方法就是採用單線程模型來處理UI操做,那麼Android中子線程必定不能更新UI控件嗎?async
其實Android系統在更新UI控件時,會調用ViewRootImpl類的checkThread()來檢測當前線程是不是建立UI控件的線程,若是不在同一個線程就會拋出異常。ide
// ViewRootImpl.java public ViewRootImpl(Context context, Display display) { mThread = Thread.currentThread(); // ... 省略無關代碼 } @Override public void requestLayout() { checkThread(); // ... } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
二、只要咱們執行下面這段代碼就會拋出第二條崩潰日誌oop
new Thread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "handler", Toast.LENGTH_LONG).show(); } }).start(); // 拋出:java.lang.RuntimeException: Can't toast on a thread that has not called
看到這裏不少人感到奇怪,子線程中更新UI控件應該是第一條崩潰日誌啊。
來看下Toast源碼:
Toast.java public Toast(@NonNull Context context, @Nullable Looper looper) { // 建立TN mTN = new TN(context.getPackageName(), looper); // ... } private static class TN extends ITransientNotification.Stub { final Handler mHandler; TN(String packageName, @Nullable Looper looper) { // ... // 看到這裏,我相信你們知道爲何緣由了。 if (looper == null) { // Use Looper.myLooper() if looper is not specified. looper = Looper.myLooper(); if (looper == null) { throw new RuntimeException( "Can't toast on a thread that has not called Looper.prepare()"); } } mHandler = new Handler(looper, null) { @Override public void handleMessage(Message msg) { } }; } }
閱讀Toast源碼後發現,彈toast時須要獲取當前線程的Looper,若是當前線程沒有Looper,就會拋出異常。看到這裏會有一個疑問,爲何在main線程中彈toast不會報錯呢?
咱們能夠猜測,既然在main線程沒有報錯,那麼確定main線程中已經建立過Looper對象。是誰建立的呢?
在Android系統中,App啓動入口是在ActivityThread類的main方法。
ActivityThread.java public static void main(String[] args) { // 建立Looper對象和MessageQueue對象,用於處理主線程的消息 Looper.prepareMainLooper(); // 建立ActivityThread對象 ActivityThread thread = new ActivityThread(); // 建立Binder通道 thread.attach(false, startSeq); // 主線程的Handler if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); // 消息循環運行 throw new RuntimeException("Main thread loop unexpectedly exited"); }
閱讀ActivityThread源碼後,咱們知道main線程的Looper在App啓動時就幫咱們建立了,因此咱們能夠直接在main線程中彈toast了。 Binder 線程會向 H(就是main線程的Handler) 發送消息,H 收到消息後處理Activity的生命週期,因此在主線程中能夠直接更新UI控件了。
主線程的消息循環模型:
ActivityThread 經過 ApplicationThread 和 AMS 進行進程間通信,AMS 以進程間通訊的方式完成 ActivityThread 的請求後會回調 ApplicationThread 中的 Binder 方法,而後 ApplicationThread 會向 H 發送消息,H 收到消息後會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執行,即切換到主線程中去執行。
先來看這段代碼,在子線程中建立Handler,運行後拋出異常了
new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(); } }).start();
出現崩潰日誌以下,提示說明:調用線程中沒有調用過Looper.prepare(),就不能建立Handler
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:200) at android.os.Handler.<init>(Handler.java:114)
爲何Looper.prepare()會影響Handler的建立呢?走進Handler的源碼分析:
//Handler.java final Looper mLooper; final MessageQueue mQueue; public Handler(@Nullable Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; // ... } --------------------------------- // Looper.java // 返回與調用線程相關聯的Looper對象 // 若是調用的線程沒有關聯Looper(也就是沒調用過Looper.prepare())返回null public static @Nullable Looper myLooper() { // Looper對象怎麼與調用線程關聯?設計到ThreadLocal知識 return sThreadLocal.get(); }
從Handler構造方法中可知:在建立Handler對象時,會檢查調用線程中是否有Looper關聯,若是沒有就拋出異常。
Looper源碼:
//Looper.java final MessageQueue mQueue; private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 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)); } // 在ActivityThread中的main方法調用 public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Looper類中主要做用:
其它的線程怎麼擁有本身的Looper呢?Android系統爲咱們提供了一個很是方便的類:HandlerThread
// HandlerThread.java public class HandlerThread extends Thread { @Override public void run() { Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } onLooperPrepared(); Looper.loop(); } }
HandlerThread繼承Thread類,當Thread.start()線程,調用Looper.prepare()後,就會建立一個Looper與這個Thread關聯。
Handler、Looper、MessageQueue、Message之間的關係:一個Thread持有一個Looper,一個Looper持有一個MessageQueue和多個與之關聯的Handler,一個Handler持有一個Looper,一個MessageQueue維護Message單項鍊表。
簡單描述上圖:使用Handler發送消息到MessageQueue,Looper不斷輪詢MessageQueue中消息,分發消息到Handler中處理。
Looper的loop()源碼:
// Looper.java 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; // ... for (;;) { // 堵塞調用,從MessageQueue中獲取消息 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // ... // 消息的分發及處理 msg.target.dispatchMessage(msg); // ... } }
Looper不斷從MessageQueue中輪詢到消息後,分發給Handler處理。
// Handler.java public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
分發消息Message處理的順序:
總結:若是想攔截Handler中callback或者Handler中handleMessage方法,能夠給Message設置callback,這樣Handler中Message處理就不會被執行。
接下來分析關鍵的MessageQueue中的next():
Message next() { // ... int nextPollTimeoutMillis = 0; for (;;) { // 阻塞在native層 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { 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) { // msg定義的when尚未到,讓native繼續等nextPollTimeoutMillis時長 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 有到點的msg,返回給Looper的loop處理 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg; } } else { // 沒有消息,在native層阻塞 nextPollTimeoutMillis = -1; } // 若是調用了quit(),Looper的loop()就會退出無限循環 if (mQuitting) { dispose(); return null; } } // ... // 當在處理idle handler的時,能夠發送一個新的Message // nextPollTimeoutMillis設置爲0,當即查詢掛起的消息,無需等待 nextPollTimeoutMillis = 0; } }
一、 Looper.loop() 死循環爲何不會致使應用卡死?
這裏就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法裏,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往 pipe 管道寫端寫入數據來喚醒主線程工做。這裏採用的 epoll 機制,是一種IO多路複用機制,能夠同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則馬上通知相應程序進行讀或寫操做,本質同步I/O,即讀寫是阻塞的。 因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。
一、直接在main線程建立Handler,那麼Looper就會與main線程綁定,子線程就能夠經過該Handler更新UI
// 使用方式 private static Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };
二、用Activity的runOnUiThread方法,判斷調用線程是不是main線程,若是不是,使用Handler發送到main線程。原理也是使用Handler機制
// Activity.java源碼 final Handler mHandler = new Handler(); public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
三、建立Handler時,在Handler構造方法中傳入main Looper。能夠在子線程建立該Handler,而後更新UI。
// 使用方式 new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // TODO 更新UI Log.d(TAG, "run: "+Thread.currentThread().getName()); // run: main } }); } }).start();
四、View.post(Runnable action),先看下源碼API 29:
// View.java源碼 public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; } private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
在來看下HandlerActionQueue的源碼:
// HandlerActionQueue.java源碼 private HandlerAction[] mActions; private int mCount; 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++; } } // 關鍵:executeActions調用時機不一樣,可能致使不會被執行 public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; } }
這種方式有不靠譜的地方,區別在於API 24(Android7.0)以上,可能post()的Runnable,永遠不能運行。
根本緣由在於,API 24(Android7.0)以上 executeActions() 方法的調用時機不一樣,致使 View 在沒有 mAttachInfo 對象的時候,表現不同了。而executeActions()執行只會在View.dispatchAttachedToWindow()方法中調用
若是你只是經過 new 或者使用 LayoutInflater 建立了一個 View ,而沒有將它經過 addView() 加入到 佈局視圖中去,你經過這個 View.post() 出去的 Runnable ,將永遠不會被執行到。
舉一個例子說明問題:
private ViewGroup mRootLayout; private Handler handler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRootLayout = findViewById(R.id.rootLayout); final View view = new View(this); Log.e("View.post()>>>>", "當前設備的SDK的版本:" + Build.VERSION.SDK_INT); view.post(new Runnable() { @Override public void run() { Log.e("View.post()>>>>", "直接new一個View,而後post的Runnable被執行了"); } }); handler.postDelayed(new Runnable() { @Override public void run() { Log.e("View.post()>>>>", "delay addView被執行了"); mRootLayout.addView(view); } }, 1000); } }
在API 24(Android7.0)如下運行結果。從執行結果中的時間能夠看出,new出來的View,post的Runnable當即被執行了。
11-04 14:40:20.614 View.post()>>>>: 當前設備的SDK的版本:19 11-04 14:40:20.664 View.post()>>>>: 直接new一個View,而後post的Runnable被執行了 11-04 14:40:22.614 View.post()>>>>: delay addView被執行了
而在API 24(Android7.0)以上(包括7.0)運行的結果。從執行時間上能夠看出,post的Runnable沒有當即被執行,而是addView後才被執行。
2019-11-04 14:44:36.240 View.post()>>>>: 當前設備的SDK的版本:28 2019-11-04 14:44:38.243 View.post()>>>>: delay addView被執行了 2019-11-04 14:44:38.262 View.post()>>>>: 直接new一個View,而後post的Runnable被執行了
經過Handler能夠發送的Message的方法以下:
public final boolean sendEmptyMessage(int what) public final boolean sendEmptyMessageDelayed(int what, long delayMillis) public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) public final boolean sendMessage(@NonNull Message msg) public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) public final boolean post(@NonNull Runnable r) public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) public final boolean postDelayed(@NonNull Runnable r, long delayMillis) public final boolean postAtFrontOfQueue(@NonNull Runnable r)
這些方法最終調用的是enqueueMessage方法
// Handler.java源碼 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
Handler源碼分析到這了,可是它的精髓遠遠不止這些,還有不少鮮爲人知的祕密,須要咱們去探索。
下一篇:Handler擴展知識探索~~~
若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)