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。其值是用来在Loop::loop()函数取值时候判断是否满足条件,满足条件即取出,不满足跳过。其时间计算方式为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、Handle::sendMessage(Message m)和Handle::post(Runnable r)

这两个函数都可以在主线程刷新任务,但是有什么区别?首先我们知道Handle::sendMessage(Message m)可以在重写的方法handleMessage(Message msg)中收到消息,但是需要注意的Handle::post(Runnable r)这个需要传入Runnable。然后最终处理的逻辑也是在Runnable中触发,并不会发送到handleMessage(Message msg1)函数中,这是结论。那么看到结论你会怎么想呢?为什么会出现这个情况,会不会认为是直接启动了一个线程去做的?
首先来说即使是Handle::post(Runnable r)函数也是会给MessageQueue添加一个Message的。所以也是会执行之前的轮询Message的那一套流程。可以参考如下代码:

public class Handler{
    
    
	...
	 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;
    }
	...
}

可以看到Runnable是赋值给Message的一个参数了。那么什么时候执行这个Runnable呢?之前知道消息轮询完后最终会调用Handle::dispatchMessage(@NonNull Message msg)进行消息分发,这里看下代码

public class Handler{
    
    
	...
	   public void dispatchMessage(@NonNull Message msg) {
    
    
        if (msg.callback != null) {
    
    
            handleCallback(msg);
        } else {
    
    
            if (mCallback != null) {
    
    
                if (mCallback.handleMessage(msg)) {
    
    
                    return;
                }
            }
            handleMessage(msg);
        }
    }
     private static void handleCallback(Message message) {
    
    
        message.callback.run();
    }
	...
}

可以看到当callback,也就是之前传入的Runabble不为空时候,执行Runnable而不是分发给Handler::handleMessage(Message msg)函数。这里需要注意的是这里没有执行异步任务,Runnable并没有在Thread中执行。这里可以尝试以下简单的测试代码:

class MainActivity: AppCompatActivity(){
    
    
 override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
 		Log.e("YM--->","---->UI线程ID:${
      
      Thread.currentThread().id}")
        val runnable = MyRunnable()
        runnable.run()
        Thread{
    
    
          	Thread.sleep(100)//延迟下保证顺序
            val runnable1 = MyRunnable()
            runnable1.run()
        }.start()
 }

  inner class MyRunnable: Runnable{
    
    
        override fun run() {
    
    
            Log.e("YM","---->异步任务MyRunnable的线程Id:${
      
      Thread.currentThread().id}")
        }
    }

}

执行结果如下:

E/YM--->: ---->UI线程ID:2
E/YM: ---->异步任务MyRunnable的线程Id:2
E/YM: ---->异步任务MyRunnable的线程Id:451

可以看到实例化的Runnable不会单独启动一个线程,只会运行在所处的线程。假如该环境是UI线程,那么Runnable运行的环境是主线程,这里的话就需要注意一个地方,如果Handle::post(Runnable r)在异步任务中执行的话那么,这个环境是主线程还是子线程?如下代码:

    Thread{
    
    
            Log.e("YM--->","---->异步任务Thread:线程ID:${
      
      Thread.currentThread().id}")
            handle?.post {
    
    
                Log.e("YM--->","---->异步任务post:线程ID:${
      
      Thread.currentThread().id}")
            }  
        }.start()

执行结果如下:

E/YM--->: ---->UI线程ID:2
E/YM--->: ---->异步任务Thread:线程ID:436
E/YM--->: ---->异步任务post:线程ID:2

这又是为什么呢?答案就是。上文已经解释了消息传递过程,所以知道Loop::loop()是在UI线程执行的,所以在UI线程中取出Meesage,然后执行Runnable当然还是在UI线程中了!

11、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()添加的任务会放在绘制任务之后(该源码需要进行确认)

猜你喜欢

转载自blog.csdn.net/Mr_Tony/article/details/122806923