Handle原理

文章目录

一、前言

Handle原理是一个老生常谈的事情,这里对其整个流程简单记录一下。这份理解是基于SDK31版本

二、示例代码

​ 对于Handle来说,主要有两个功能,一个是延迟处理消息,一个是线程间切换(参考链接:https://developer.android.google.cn/reference/android/os/Handler?hl=en)。这里简单定义个Handle来进行说明:

Handle有两种定义场景。一种是UI线程,一种是子线程。

UI线程

在主线程创建的话可以在子线程发送消息到主线程

class MainActivity : AppCompatActivity() {
    private var handle: Handler = object : Handler(Looper.myLooper()!!){
        override fun handleMessage(msg: Message) { //接受消息
            super.handleMessage(msg)
            Log.e("YM","-->what:${msg.what}")
        }
    }
  
     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        Thread{
            handle.sendEmptyMessage(10) //发送消息
        }.start()
    }
}

子线程

以下的例子是建立一个两个子线程直接相互通信的示例

class MainActivity : AppCompatActivity() {
  private var handle: Handler ?= null
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
     		ThreadA().start()
        ThreadB().start()
  }
  inner class ThreadA : Thread(){
        override fun run() {
            super.run()
            Looper.prepare()
            handle = object : Handler(Looper.myLooper()!!){
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)
                    Log.e("YM--->ThreadA","--->接受的参数:${msg.what}")
                }
            }
            Log.e("YM--->ThreadA","--->循环开始")
            Looper.loop()
            Log.e("YM--->ThreadA","--->循环结束")
        }
    }
    inner class ThreadB : Thread(){
        override fun run() {
            super.run()
            Log.e("YM--->ThreadA","--->发送消息")
            handle?.sendEmptyMessageDelayed(12,100L)
        }
    }
}

三、问题

​ 看这篇文章的前提,是希望大家对MessageMessageQueueLoopHandle有个基本的概念。大致的意思如下(纯属个人理解,跟其他人理解可能不太一样)

  • Message: 一个消息实体,Handle进行发送消息就是发送的这种格式的消息
  • MessageQueue:消息队列,用来存储消息的(说个题外话,其实消息队列没存这里面,而是采用链表方式存在Message里面,这个稍后再说)。
  • Loop:用来轮训消息的,并分发给Handle
  • Handle: 这个就是用来发消息,对消息做些简单的加工,然后将拿到的消息回传给业务代码。相当于管理器的作用。

我们先看第一个问题,就是Handle是如何将Message传递过去的

1、消息是如何传递的?

​ 当我们调用Handle::sendEmptyMessage()函数时候,该消息会进入Handle中进行发送消息,该消息在Handle中会调用MessageQueue::enqueueMessage(Message msg, long when)将消息添加进去。在MessageQueue::enqueueMessage(Message msg, long when)函数中会发现,最终是调用Message.next字段进行赋值到下一个。所以MessageQueue类只是对消息存储的一个管理类,最终存储是在Message中的。通过查看Message源码可以知道,该方式是采用链表的方式进行数据存储,并不是集合的方式进行存储的。这里会延伸出几个问题,首先因为有延迟消息,也有立刻执行的消息,那么链表的排序规则是什么?

2、Message消息的排列规则是什么?

​ 上个问题解释了在Handle机制中是使用链表这个数据结构进行消息存储的,那么消息排列的规则是什么呢?是如何处理延迟消息和立刻发送的消息的?在上一个问题中可以知道不管哪一类消息最终都会调用MessageQueue::enqueueMessage(Message msg, long when)进行存储消息的,该函数中有一个变量when。其消息排列规则既是通过该时间降序排列进行判断,时间小的排在前面,时间大的排列在后面。这样就保证了消息按照顺序取出。其时间计算方式为SystemClock.uptimeMillis() + delayMillis。默认为0。到此为止就可以知道Handle如何将消息进行存储的。(SystemClock.uptimeMillis()为开机到现在的时间,单位为毫秒)

3、消息是怎么发送给Handle的?

​ 上个问题解释了消息是如何存储的及其存储规则,那么消息该如何发送给Handle?通过官方文档或者源码说明或者网上其余的参考资料可知,是通过Loop::loop()函数进行循环取出消息链表中的消息的。该函数最终是调用Loop.loopOnce(final Looper me,final long ident, final int thresholdOverride)进行遍历循环的----通常来说,我们是在重写的Handle::handleMessage(msg: Message)函数中接受到消息的,在源码中,该函数为空函数,最终由Handle::dispatchMessage(@NonNull Message msg)函数调用。而在Loop.loopOnce(final Looper me,final long ident, final int thresholdOverride)函数中通过Message msg = me.mQueue.next();获取目标的消息,最终调用了msg.target.dispatchMessage(msg);。其中Message.target参数为Handle。这样就将消息传递给了业务代码。

4、Loop和MessageQueue和Handle的关系是什么?

​ 上述的几个问题解释了消息的传递流程,但是有一些地方没有解释清楚,比如Loop是如何将Handle和线程以及MessageQueue绑定在一起的。通过查看源码可知Loop类中有一个变量static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();。该变量是由静态的ThreadLocal存储了Loop对象。所以各个地方都可以使用同一个变量,而不会出现数据不一致的情况。由于ThreadLocal特性的问题,可以解决每个线程对数据的访问隔离,举个例子就是A线程对ThreadLocal进行赋值,B线程是取不出来的。这样就可以在线程中通过判断有没有值来判断是不是绑定了,这样就将Loop和线程绑定在一起了。在通过Loop::prepare()函数进行创建Loop时候最终调用了私有的构造函数,如下:

  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可以看到这时候创建了MessageQueue对象,因此这时候是将LoopMessageQueue进行了绑定。由于创建Handle的时候需要将Loop传入进去(无参构造函数被废弃了,因为有时候会出现Loop为空的情况,所以官方建议传入Loop),这样就完成了HandleLoop的绑定。

这样可以在Handle里面取到Loop及其MessageQueue

5、延迟消息是什么时候发送的?

​ 上述问题已经解释了每个Message里面都有一个when参数,当该参数的时间不到时候,循环空转,直至时间满足条件才会进行发送消息。

6、如果创建一个新的Message也会有Handle吗?

​ 上文说到了最终回调用Message.targe.dispatchMessage(msg)将消息传递给Handle,那么如果new Message()也会有Handle吗?这个是的,因为最终会传递给Handle。只要在里面发送消息之前赋值即可。参考代码如下:

 public class Handler { 
   ...
	private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this; //赋值Handle
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
   ...
 }

7、没有消息的时候会不会停掉Loop

​ 这个问题问法有很多种,这里解释下过程。首先轮询是需要通过Loop.loop()进行开启,这个Loop.loop()是一个死循环for(;;),所以没有数据就就会空跑起来,一直到有数据为止。除非Handle结束了或者退出才会终止循环。Message msg = me.mQueue.next();该代码中的next()函数也是有一个死循环for(;;)。这样就不会出现取不到数据返回null的情况。

8、死循环不会导致ANR吗?

​ ANR的情况只会出现在生命周期函数里面出现大量耗时操作才会出现ANR,但是Loop.loop()没有在生命周期里面。还有一个需要注意的地方就是UI线程也是一个线程,如果任务结束了,那么线程就退出了,如果UI线程退出了,那么程序就会结束了,换而言之,也是Loop.loop()是程序一直在运行。而且生命周期触发其实也是通过Handle进行触发的。在程序启动时候最终会通过ActivityThread启动一个UI线程,其入口为main函数,Loop就是就是在这里进行初始化以及开始轮询的。

9、Handle是如何进行线程间切换

上面的问题已经说了会通过Handle::dispatchMessage(@NonNull Message msg)。这个函数是定义在Handle定义的线程里面的,通过Message进行调用,而Message是通过Loop进行轮询,Loop又是通过ThreadLocal进行保存的,由于线程隔离,所以最终会回调到其创建时候的线程里面。同时还需要理解另外两部分,一部分是存message,一方面是取message。存是子线程存的,取是通过绑定线程的Loop取的。就好像是放了一份共同的数据,一个线程存,一个线程取一样。这份共同的数据就是MessageQueue。因为Loop不知道什么时候有数据,所以在需要不停的轮询,因为轮询操作是在UI线程,所以取到数据后面执行的代码也是在UI线程。所以其实没有存在线程切换的问题,只是外在感知为切换而已。

这里面可以尝试以下简化版的线程切换代码:

class ThreadLocalTest {

    @Test
    fun test(){
        val runnableA = ThreadA()
        Thread.sleep(50)
        val runnableB = ThreadB()
        Thread(runnableA).start()
        Thread.sleep(50)
        Thread(runnableB).start()
    	Thread.sleep(5000)
    }
    private var handle: MyHandle ?= null
    inner class ThreadA : Runnable{
        override fun run() {
            handle = object : MyHandle(){
                override fun dispatchMessage(msg: String){ //用于分发消息
                    println("YM------>收到的消息:$msg, threadId: ${Thread.currentThread().id}")
                }
            }
            println("YM--->ThreadA-->创建时候的线程ID:${Thread.currentThread().id}")

            handle?.loop()
        }
    }

    inner class ThreadB() : Runnable{
        override fun run() {
            println("YM--->ThreadB-->创建时候的线程ID:${Thread.currentThread().id}")
            handle?.sendMessage("=======")
        }
    }
}
open class MyHandle{
    private val messageList = Vector<String>() //存储消息集合,这里使用线程安全的集合,为了防止多线程出现问题,在Handle是使用synchronized处理
    open fun dispatchMessage(msg: String){ //用于分发消息

    }

    fun sendMessage(msg: String){
        messageList.add(msg)
    }

    //开始循环
    fun loop(){
        while (true){
            Thread.sleep(15)//这里是为了测试,所以不让循环次数太多
            for (index in 0 until messageList.size){
                val value = messageList[index]
                dispatchMessage(value)
                messageList.remove(value)
            }
        }
    }

}

执行结果

YM--->ThreadA-->创建时候的线程ID:24
YM--->ThreadB-->创建时候的线程ID:25
YM------>收到的消息:=======, threadId: 24

10、View::post()

​ 有时候会通过View::post()函数进行线程切换,其实内部也是使用Handle进行切换的。这里可以看下官方是怎么使用Handle

​ 首先View中的Handle函数是定义在内部静态类AttachInfo中,该值是构造函数的必传参数。该参数在View中的全局变量为mAttachInfo,其赋值函数为View::dispatchAttachedToWindow(AttachInfo info, int visibility)。该函数是通过ViewRootImpl进行进行调用的。通过代码可知只有第一次加载进窗口的时候才会初始化。通过源码顶部的注释可知,该类是整个视图顶部的层次结构,所有View加载都会通过该类进行处理。所以当View第一次加载进窗口的时候就已经有Handle了。关于View先到此结束,继续Handle的话题。View内部的Handle是在ViewRootImpl中进行定义的。然后其为一个继承于Handle的子类ViewRootHandle。除此之外暂时没有发现有需要特别注意的地方。有一个需要注意的就是post()添加的任务会放在绘制任务之后(该源码需要进行确认)

上一篇:Windows 进程的内核对象句柄表


下一篇:go panic 和 recover