我们都知道,Android只允许在Main线程更新UI操作,否则会在ViewRootImpl#checkThread()
里抛出Only the original thread that created a view hierarchy can touch its views.
,对此Android提供了Handler
来异步处理消息线程。
我们先来看下如何创建Handler对象
class HandlerActivity : AppCompatActivity() {
lateinit var mHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
mHandler = Handler()
Thread {
mHandler.post {
//更新UI操作
}
}.start()
}
}
代码很简单,创建了Handler对象然后开启线程,再执行更新UI操作,当然只满足于此显然是不够的,搞清楚为什么post里可以更新UI需要从源码角度去分析。
万事开头难,我们先来看下 Handler创建过程
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) { //代码1
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(); // 代码2
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到,无论那种构造方法最终都会调用双参数的重载构造,代码1
用来检查可能的内存泄露,FIND_POTENTIAL_LEAKS
恒定为false,代码2
通过Looper#myLooper()
静态方法得到一个mLooper对象,如果mLooper为Null,则会抛出一个异常,我们看下myLooper()做了什么,代码如下
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
很简单,只是从sThreadLocal得到Looper对象,它唯一的set是在Looper#prepare()中,如下所示
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,如果已经存在则抛出异常,不存在创建一个Looper放进去。这也解释了一个线程最多只有一个Looper。
看到这里你可能会有一个疑问,我们在创建Handler时有一句这样的代码mLooper = Looper.myLooper(); // 代码2
,这里并没有抛出异常,难道Main线程已经有人帮我们preaper了?,带着这个疑问我们一起看下ActivityThead#main()方法,它是Android入口函数,如下简化所示
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //代码1
...
Looper.loop();
....
}
代码1
调用了prepareMainLooper(),而这个方法又调用了Looper#prepare(false)
,代码如下所示
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#prepare()
到此Handler创建完成,总结一下就是在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。
接下来我们看下Handler#post()方法怎样更新UI的,代码如下
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
这里的post作为一个对外封装,首先通过getPostMessage(r)
得到一个Message对象,把当前的r赋值给m.callback,然后post内部最终调用Handler#sendMessageAtTime(Message,long)
,代码如下
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //代码1
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); //代码2
}
我们先看下参数,msg是通过getPostMessage(r)创建的,是我们要发送的对象,
uptimeMillis代表延迟时间,这里等于0。代码1
mQueue在Handler里的构造方法中赋值的,使用的是Looper#mQueue
属性,该属性在创建Looper时生成,因此一个Looper对应一个MessageQueue它是一个队列。代码2
看名字就知道是入队方法,代码如下
#android.os.Handler#enqueueMessage()
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,this是调用者Handler实例,这意味着一个线程中可以有多个Handler且处理消息互不干扰,然后把this存储到msg.target中,用来标识消息的来源,接着执行真正的入队方法,代码如下
# android.os.MessageQueue#enqueueMessage()
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; //代码1
boolean needWake;
if (p == null || when == 0 || when < p.when) { //代码2
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { //代码3
// 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;
}
这里代码较长,我们一点一点看,先看下参数msg是我们入队的消息,when是之前的uptimeMillis延迟时间。代码1
是待处理得消息,也就是队列的头节点,通过这个节点可以找到队列的全部元素。代码2
所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。代码3
当msg不是头节点时,通过遍历方式找到msg位置。
入队的操作我们看完了,那出队的操作在哪里呢?还记得ActivityThread#main()
方法吗?这里我再次复制下,代码简化如下
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //代码1
...
Looper.loop(); //代码2
....
}
没错,就是这里,在代码2
处为了应用程序存活开启了一个死循环,如果没有这个loop想象一下会什么样,应用执行完主线程的代码后,就退出了,现象会是一闪而过。因为有了这个loop()方法才能等着被唤醒,然后等待着ActivityManagerService唤醒生命周期等,再执行相应的生命周期,因此loop()方法都在主线程中执行,我们进入到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; //代码1
...
for (;;) { //代码2
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg); //代码3
} finally {
...
}
...
msg.recycleUnchecked();
}
}
可以看到代码1
就是我们入队操作的队列,这里是同一个,代码2
通过一个死循环一直遍历queue.next(),这是一个堵塞方法。我想你已经猜到,这就是出队方法,如果空则表明MessageQueue#mQuitting为true,跳出死循环,当然默认是不会跳出的,除非你手动调用Looper#quit()
方法,这里可以看出子线程需要调用该方法释放资源。然后将代码3
拿出的msg交给dispatchMessage(),其中msg.target就是Handler,接下来看下代码3如何实现的
#android.os.Handler#dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);//代码1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { //代码2
return;
}
}
handleMessage(msg); //代码3
}
}
这里可以看到,上面的代码很简单,很容易看出调用优先级msg.callback -->mCallback.handleMessage(msg) -->handleMessage。mCallback是创建Handler时传入的,当我们传入mCallback后自然就不会再执行Handler#handleMessage()方法了,那么msg.callback是个什么东西呢?哦!原来是Runnable,那我们继续进入handleCallback(msg)看下吧
private static void handleCallback(Message message) {
message.callback.run();
}
卧槽,这么简单,只是把run当成一个回调,并没有start()。这里已经实现线程切换,这里再提一嘴View#post()和Activity#runOnUiThread()它们都是基于Handler#post方法,所以不再继续分析。
到此已经完成了开篇处的解析,但不应满足于此,那么我们还是要来继续分析一整个异步消息处理流程的示意图如下图所示: Handler工作流程图总结
- 一个线程最多只能有一个Looper,一个Looper中只能有一个MessageQueue
- 一个Message只能属于一个Handler,使用target属性标识
- 同一个Handler只能处理自己发送给Looper的那些Message
本文大部分内容来源于郭婶:https://blog.csdn.net/guolin_blog/article/details/9991569
网友评论