Android Handler消息机制源码分析

Android Handler消息机制源码分析

Android小彩虹2021-07-29 4:41:35380A+A-

一,前言

众所周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread)。

如此设计原因有二:

(1) 由于UI操作的方法都不是线程安全的,如果多个线程都可以更新UI,会造成UI界面混乱。

(2)UI操作都加锁,线程同步的话,非常影响性能。

故Android干脆只允许在单个线程-->主线程中操作UI, 否则会报android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建该view的线程才可以改变该view的状态。

Google 为上述单一线程更新UI设计了Handler消息机制,主要场景是为了更新UI,更广泛的应用场景是线程之间进行通信。

二, Handler消息机制

        Handler 消息机制主要包含三个重要角色:Handler, MessageQueue, Looper

        Handler 发送消息,压入Messagequeue,Looper 作为轮询器,不停地从MessageQueue中获取Message,并再次分发dispatchMessage给Handler, 由handler的handleMessage()方法处理。


1, Handler  消息发送处理者

         Handler持有一个Looper对象和一个MessageQueue对象(即looper.mQueue), 在创建Handler对象时可以通过指定Looper(如果不指定,默认为当前线程的Looper对象 ),来确定Handler为哪个目标线程服务: 发送消息到该目标线程,并在目标线程里处理消息。 

        Handler提供了多种发送消息的方法,它们之间的调用关系如下

在post(Runnable r)   / postDelayed(Runnable r,  long delay)  / sendMessage(msg)里调用的是sendMessageDelayed(msg, delay ) ,进而sendMessgaeAtTime(msg, SystemClock.uptimeMillis()+ delay)。

其中post方法是将runnable 通过getPostMessageRunnable(r) 转化为一个Message对象,msg.callback = r;

最终通过enqueueMessage()将消息压入队列中。可以看到此处将message.target 指定为该handler,(后面会讲target的作用),然后调用了MessageQueue.enquqeueMessage()

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis){  

      msg.target = this; 

      return queue.enqueueMessage(msg, uptimeMillis);

}     

另外Handler提供了可以将消息发送到队头的特殊方法, 这样可以使得消息可以优先被处理,postAtFrontOfQueue(msg)、 sendMessageAtFront|OfQueue(msg), 其实本质也一样,只不过消息的时间戳设为0, 最终是sendMessgaeAtTime(msg, 0)

2, Looper 轮询者

       Looper作为轮询器,提供的都是静态方法。

        (1)   Looper.prepare() 初始化

        内部创建一个Looper对象,并存在ThreadLocal里。ThreadLocal是线程的数据隔离区,可以存放各个线程单独的数据,存取ThreadLocal数据,不受多线程影响。 new Looper()时,looper对象持有一个新建的MessageQueue对象 和 当前线程对象(Thead.currentThread())。sThreadLocal.set(new Looper() ) ; 

        (2)   Looper.loop()  循环获取messageQueue里的消息

         里面有一个死循环for(;;)    去调用   messageQueue.next() 方法获得队头的msg, 然后分发给handler去处理。loop()方法是一个无限循环,之后的代码不会再执行。

        (3)Looper.getMainLooper()  获取UI线程的Looper对象 

              Looper.myLooper()获取当前线程的Looper对象

       主线程的looper对象早在ActivityThread启动的时候通过prepareMainLooper()创建好了,因此每次直接获得即可。而获得当前线程的looper对象也很简单, 直接取sThreadLocal存放的looper对象,与prepare()相呼应,sThreadLocal.get()。

3, MessageQueue消息队列

        虽然看起来MessageQueue只是一个链表存储的消息容器,但是真正的处理方法都封装在这个类里, 发送消息最终调用的是enqueueMessage(), 轮询消息调用的是next()。这里也巧妙的使用了生产者消费者模式。

       enqueueMessage(msg,uptimeMillis)方法 :将消息压入队列,每个消息有时间戳,, msg.when = uptimeMillis;按照uptimeMillis的大小从小到大依次排序插入队列中,uptimeMillis相等的,则先入队列的先处理。

      next()方法:获取队列的头部message,即链表的header,值得注意的是,next()方法里也有一个死循环,直到取到满足条件的message才跳出循环,这个条件就是message的执行时间要小于当前时间,message.when < now。如果msg.when <= now, 则不阻塞,直接取出,这不必说, 如果msg.when > now, 就进到下一轮循环里, 直到msg.when <= now.

4, Message消息

      一个数据model 本来没什么要说的,但是它有自己的神奇之处。它所能携带的信息自不必说,what唯一标示消息种类,arg1,arg2用于携带简单的数字,obj携带对象类数据, setData(Bundle)携带更复杂的数据。

       在Message里有一个实例变量target,target是个handler对象,非常关键, 轮询获取到msg后, msg自调target的dispatchMessage()方法, 继而回调handleMessage()方法处理消息。 这里要注意的是处理消息有一个顺序: 首先msg的callback如有先处理,其次handler创建时带的callback如有则处理,最后才轮到handleMessage()方法处理。 

     

                                                   源码结构示意图(可放大查看)

要点:

1,一个线程有且仅有一个Looper和一个MessageQueue!!! 但是可以有多个Handler, 一个msg到底由哪个handler对象来处理,会通过msg.target 来决定

2, Looper.loop()方法一定要在prepare()之后调用,先初始化了才能调,并且 Looper.loop()  之后的代码不能执行,因为是个死循环for(;;) {mQueue.next();  源码可以看到 ActivityThread的main方法里最后一句就是loop()方法; 我们在自己实现子线程的消息通信时,一定要记得这点。(当然HandlerThread推荐给你,本质是一个封装好Looper的Thread子类)

3,MessageQueue里有很多native方法,最主要的两个方法next()和enqueueMessage(),mQueue.next() 方法里也有一个死循环,只有获取到一个有效的msg才跳出循环。

4, Handler构造方法里mLooper = Looper.myLooper();若不指定,则为当前线程, 因为是从ThreadLocal里取出的Looper对象

5,Handler sendMessage→ sendMessageDelayed()→sendMessageAtTime()
每个消息是有时间戳的,postAtFrontOfQueue(),时间戳的值=0,而一般消息的时间戳是当前系统时间,

6,处理消息有一个顺序: 首先msg的callback如有先处理,其次handler创建时带的callback如有则处理,最后才轮到handleMessage()方法处理。 其中post(r), getPostMessage(r)就将r作为msg的callback发出去一个msg。 这里肯定优先执行r,而不是handleMessge()方法。

三, HandlerThread

    本质是一个封装好了Looper的Thread子类。HandlerThread extends Thread , 持有一个成员变量mLooper,提供有getLooper()方法,其中在Thread的run() 方法中代()码如下,很简洁,可以看到Looper.loop()方法是最后一行。

@Override 
public void run() {   
    Looper.prepare();    
    synchronized (this) {       
        mLooper = Looper.myLooper();        
        notifyAll();    
    }       
     Looper.loop();    
}

使用HandlerThread的方法

   HandlerThread handlerThread = new HandlerThread();        
   handlerThread.start();
   Handler handler = new Handler(handlerThread.getLooper()) {
        @Override   
        public void handleMessge() { 
        }
   };
   handler. sendMessage(msg);
注意: handler的创建必须在HandlerThread.

Handler的创建必须在handlerThread.start()之后,由源码中可以看到Looper的初始化是在handlerThread的run()方法里!!









点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们