Android Handler异步消息
前言
在Android中,经常会遇到线程间通信的场景,下面就说说Android中最重要的异步消息机制Handler
异步消息机制Handler
Handler是Android中最重要的异步消息机制,总共由四部分组成:Handler,Message,MessageQueue,Looper
1、主线程创建 Handler 对象(如果在子线程创建,必须保证调用了Looper.prepare()),并重写 handleMessage() 方法。
2、子线程创建 Message 对象,通过第一步创建的Handler 发送消息,handler.sendMessage(message),handler将消息发送到MessageQueue中。
3、Looper 通过loop()循环从 MessageQueue 中取出待处理消息。
4、looper将取出的message分发回 Handler 的 handleMessage() 方法中处理。
Looper
创建 Looper 的方法是调用 Looper.prepare() 方法。注意:在 ActivtyThread 中的 main 方法为我们 prepare 了
public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); //... Looper.prepareMainLooper(); //初始化Looper以及MessageQueue ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } 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.prepareMainLopper()
public static void prepareMainLooper() { prepare(false);//调用prepare(), 消息队列不可以quit synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Looper.prepare(boolean quitAllowed)
public static void prepare() { prepare(true);//消息队列可以quit } // 私有的构造函数 private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) {//不为空表示当前线程已经创建了Looper throw new RuntimeException("Only one Looper may be created per thread"); //每个线程只能创建一个Looper } // ThreadLocal 多线程中重要的知识点,线程上下文的存储变量 sThreadLocal.set(new Looper(quitAllowed));//创建Looper并设置给sThreadLocal,这样get的时候就不会为null了 }
**注意:**一个线程只要一个Looper。如何保证只有一个?
这里用到了ThreadLocal。一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。所以利用ThreadLocal可以保证一个线程只有一个Looper。
为了保证 ThreadLocalMap.set(value) 时,value 不会被覆盖(即Looper不会改变),会先进行上面代码的 if 操作,if (sThreadLocal.get() != null) , 不为空表示当前线程已经创建了Looper,然后直接抛异常结束prepare。如果 if 不成立,则 new Looper()。
MessageQueue
MessageQueue的创建是在Looper构造函数中
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue mThread = Thread.currentThread(); //当前线程的绑定 } MessageQueue(boolean quitAllowed) { //mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的 quit()方法 //省略... mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
Looper.loop()
在 ActivtyThread的main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 开始轮询
public static void loop() { final Looper me = myLooper();//里面调用了sThreadLocal.get()获得刚才创建的Looper对象 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }//如果Looper为空则会抛出异常 final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { //这是一个死循环,从消息队列不断的取消息 Message msg = queue.next(); // might block if (msg == null) { //由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage enqueueMessage后 //队列里才有消息 // 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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg);//msg.target就是绑定的Handler,详见后面Message的部分,Handler开始 //后面代码省略..... msg.recycleUnchecked(); } }
loop方法开启后,不断地从MessageQueue中获取Message,对 Message 进行 Delivery 和 Dispatch,最终发给对应的 Handler 去处理(通过msg.target找到对应的handler)。
**说明:**MessageQueue队列中是 Message,在没有 Message 的时候,MessageQueue借助Linux的ePoll机制,阻塞休眠等待,直到有Message进入队列将其唤醒。
Handler
handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue。
最常见的创建 handler 的方式:
//第一种方式 Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; //第二种方式(looper作为参数传入) Handler handler=new Handler(Looper looper)
第一种方式在内部调用 this(null, false);
public Handler(Callback callback, boolean async) { //前面省略 mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**! if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue;//创建消息队列MessageQueue mCallback = callback; //初始化了回调接口 mAsynchronous = async; }
Looper.myLooper() ;
//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题 // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal sThreadLocal = new ThreadLocal(); public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
sThreadLocal.get() will return null unless you’ve called prepare(). 这句话告诉我们 get 可能返回 null 除非先调用 prepare()方法创建 Looper 。在前面已经介绍了
Message
message的创建可以直接 new Message() ,但是有更好的方式 Message.obtain()。因为可以检测是否有可以复用的 Message,复用避免过多的创建、销毁 Message 对象,达到优化内存和性能的目的。
public static Message obtain(Handler h) { Message m = obtain();//调用重载的obtain方法 m.target = h;//并绑定的创建Message对象的handler return m; } public static Message obtain() { synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全 if (sPool != null) { //sPool是就是handler dispatchMessage 后 通过recycleUnchecked回收用以复用的Message Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
创建 Message 的时候通过 Message.obtain(Handler h) 这个构造方法将message和handler绑定。当然也可以在 Handler 中的 enqueueMessage() 绑定。
Handler 发送消息(消息入队)
Handler 发送消息的重载方法很多,我们用得比较多的方法就是post、sendMessage、sendMessageDelay方法,先挨个看看
post方法
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
post接受一个Runnable类型的参数,并将其封装为一个Message对象,并且将Runnable参数赋值给msg的callback字段,这里要记住,后面有用——Runnable什么时候执行的呢?
最后调用的就是sendMessageDelayed
sendMessage方法
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }
最后调用的也是sendMessageDelay,第二个参数是0。
sendMessageDelay方法
可见无论是post还是sendMessage方法,最后都走到了sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } // 这里注意第二个参数 @param updateMillis 是一个具体的时间点。 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; //…… return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { // 【关键点6】这里要注意target指向了当前Handler msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } // 【关键点7】调用到了queue#enqueueMessage方法 return queue.enqueueMessage(msg, uptimeMillis); }
最终会走到handler中的enqueueMessage方法,然后走到quene.enqueneMessage(msg, uptimeMillis),将message放入了消息队列,那么我们来看看,是如何放入队列的吧。
boolean enqueueMessage(Message msg, long when) { //…… // 【关键点8】对queue对象上锁 synchronized (this) { //…… msg.markInUse(); msg.when = when; // msg的when时刻赋值 Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. // 翻译:新的头结点,如果queue阻塞,则wakeup唤醒 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 (;;) { // for循环,break结束时when < p.when,说明按照when进行排序插入,或者尾节点 prev = p; p = p.next; if (p == null || when < p.when) { break; // 【关键点9】 找到插入位置,条件尾部或者when从小到大的位置 } 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); // native方法,唤醒 } } return true; }
这里面将msg放到消息队列中,可以看到这个队列是一个简单的单链表结构,按照msg的when进行的排序,并且进行了synchronized加锁,确保添加数据的线程安全。之所以采用链表的数据结构,原因是链表方便插入。
初看源码的时候,应该忽略掉wakeup这些处理,关注msg是如何加入队列即可。
到这里,我们了解了message是如何加入消息队列MesssageQueue。但是消息什么时候执行,以及post(Runnable)中的Runnable什么时候才执行。
消息出队执行
前面提到的Looper.loop()中,主要调用me.mQueue.next()获取一个消息msg,注意这里可能阻塞。这里调用了msg.target.dispatchMessage(msg),这里msg.target可以回头看看前面Message创建赋值的地方。所以这里就将消息分发给了对应的Handler去处理了。待会儿再看Handler.dispatchMessage,我们接着看next方法是怎么取消息的。
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 nextPollTimeoutMillis = 0; // 【关键点12】继续死循环 for (;;) { //…… 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; // msg 指向链表头结点 if (msg != null && msg.target == null) { // 这个if可以先忽略 // 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) { // 【关键点13】如果当前时间小于头结点的when,更新nextPollTimeoutMillis,并在对应时间就绪后poll通知 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; // 断链处理,等待返回 //…… 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; } //…… } //…… // 下面是IdleHandler的处理,还不是很了解 } }
next方法中也是一个死循环,不断的尝试获取当前消息队列已经到时间的消息,如果没有满足的消息,就会一直循环,这就是为什么会next会阻塞的原因。
看完了next方法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg),接着看Handler是如何处理消息的。
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { // 如果消息有CallBack则直接,优先调用callback handleCallback(msg); } else { // 如果Handler存在mCallback,优先处理Handler的Callback if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } // 此方法会被重写,用户用于处理具体的消息 handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
dispatchMessage方法中优先处理Message的callback,再回头看看Handler.post方法应该知道callback是个啥了吧。
如果Handler设置了mCallback, 则优先判断mCallback.handleMessage的返回值,这个机制可以让我们做一些勾子,监听Handler上的一些消息。
handleMessage(msg)这个方法一般在创建Handler时被重写,用于接收消息。
总结
1、一个线程只能有一个Looper,如何确保?
答案:ThreadLocal
2、Handler.post(Runnable)会将Runnable封装到一个Message中。
3、MessageQueue采用单链表的实现方式,并且在存取消息时都会进行加锁。
4、Looper.loop采用死循环的方式,会阻塞线程。那么为什么主线程不会被阻塞?
因为Android是事件驱动的,很多的系统事件(点击事件、屏幕刷新等)都是通过Handler处理的,因此主线程的消息队列,会一直有消息的。
5、Handler是如何实现线程切换的?
Looper和MessageQueue和线程绑定的,也就是说这个消息队列中的所有消息,最后分给对应的Handler都是在创建Looper的线程。所以无论Handler在什么线程发送消息,最后都回到创建Looper的线程中执行。
6、Thread和Looper、MessageQueue是一对一的关系,Looper、MessageQueue对于Handler是一对多的关系。这里要注意,一个具体的Handler实例,肯定只关联一个Looper和queue的哟。
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀