如果在主執行緒(也叫UI執行緒)中執行一些耗時操作,會出現ANR問題。為了避免ANR,需要將耗時操作,如網路請求啊、資料庫操作啊、讀取檔案等等的操作,開啟一個子執行緒來處理。
在耗時操作執行完畢後,直接在子執行緒中更新UI怎麼樣呢?
一般來說,會出現下面這個錯誤:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:19781)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
at icbc.agree.tmpapppp.MainActivity$2.run(MainActivity.java:37)
從上面的程式碼提示,不難看出UI是隻能在主執行緒中更新的,這也是為什麼主執行緒也被稱為UI執行緒的原因。
但是我們在onCreate中啟動一個子執行緒去更改一個TextView的文字,會驚奇的發現程式竟然可以正常執行,這是怎麼回事呢?
其實是因為,檢查執行緒的工作是ViewRootImpl來完成的,但是在onCreate中啟動子執行緒,可能在ViewRootImpl物件建立之前便已經執行了,TextView的setText方法,不相信的話可以在setText之前sleep一段時間看看哈。
既然不可以在子執行緒更新UI,那麼我們需要一個機制來通知主執行緒進行UI更新,這便是Handler的職責!
Handler是如何實現的呢?
要想搞明白Handler的原理,我們需要認識幾個概念:
Message:Handler傳遞和處理的資料的載體
Message Queue:訊息佇列
Looper:一個不斷從訊息佇列中取出資料的迭代器
Handler的工作流程大致如上圖所示,Handler通過sendMessage等方法,傳送一個Message,將其放到MessageQueue中,Looper不斷的取出佇列中的資料,並進行分發處理。
接下來,從安卓系統原始碼再來看一下Handler的工作原理:
首先,我們看一下主執行緒中的工作
public static void main(String[] args) {
<-省略部分不相關程式碼->
Looper.prepareMainLooper();
<-省略部分不相關程式碼->
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們看到Looper.prepareMainLooper();這行程式碼,可以到Looper中看一下它的作用,其實最終是呼叫的prepare方法,而它的作用就是建立一個訊息佇列,程式碼如下:
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
在主執行緒中的最後是呼叫的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;
<-省略程式碼部分->
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
<-省略程式碼部分->
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
<-省略程式碼部分->
}
}
我們分析下上面這段程式碼,我們獲取到Message Queue,然後開啟一個無限迴圈,通過queue.next()取出來Message,如果訊息為空,說明訊息佇列中沒有訊息了,所以停止迴圈
最後的msg.target.dispatchMessage(msg);應該很熟悉了吧?這個target就是傳送訊息的Handler。
現在我們再去看一下,Handler的部分:
無論是sendMessage還是sendEmptyMessage,最終都是呼叫的sendMessageAtTime,所以我們直接看sendMessageAtTime的原始碼部分。
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);
}
找到target了吧?有興趣的可以繼續往下找找程式碼,其實就是將Message加到訊息佇列中
好的,至此Handler完整的流程便梳理完了!