如果在主线程(也叫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完整的流程便梳理完了!