欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Android】从源码角度看Handler机制

程序员文章站 2022-07-14 15:05:30
...

在Android开发规范中,规定了主线程的任务的响应时间不能超过5s,否则会出现ANR,即程序无响应。为了避免这个问题的出现,常用的一个解决方案就是开辟新线程,在开辟出来的子线程中去处理耗时的业务,然后回到UI线程(主线程)来刷新UI,这个过程中“回到UI线程刷新UI”这一步的实现,常用到handler。因此在我的理解中,Handler、主线程、子线程之间的关系可以比喻成人操作无人机,主线程就是操作无人机的人,子线程就是无人机,而handler就是人和无人机之间的遥控器,人放出无人机去拍摄各种场景(处理耗时操作),这个过程中人(主线程)可以不受影响的做自己的事情,当无人机(子线程)处理完任务,会把数据通过遥控器(Handler)的画面传递给人(主线程),人收到画面后对画面数据进行读取和使用(更新主线程)。例子仅做辅助理解用,如果有理解偏差不到位,还望指出!感谢!
实际上网上有关多线程、并发、Handler的资料有很多,多数讲解的内容也十分深刻详细,笔者自知能力有限,本文就不再像各位大佬那样介绍对Handler的理解了,但是想从源码的角度,来分析一下Handler的工作原理,也作为自己学习的内化成果检测。
首先需要介绍几个概念:

  • MessageQueue: 消息队列,其实这里说队列有点不合适,因为实际上其内部存储并非队列的形式,而是用了一个单链表的数据结构来存储一系列消息。
  • Looper: 消息轮询器,它能够不断的访问我们的消息队列,从而提取其中的消息以供处理。实现的核心在于其loop()方法。
  • Handler: 既能接收消息,又能处理消息,主线程和子线程的桥梁。
  • Message: 消息,是数据的载体。

下面我们正向的去了解一下Handler的运行原理:


我们前面说过,Handler相当于一个主线程与子线程之间的桥梁,这个桥梁之所以能在两端之间建立联系,是因为looper的存在:
我们的 Android 应用在启动时,会执行到 ActivityThread 类的 main 方法,就和我们以前写的 java 控制台程序一样,其实 ActivityThread 的 main 方法就是一个应用启动的入口。在这个入口里,会做很多初始化的操作。其中就有 Looper 相关的设置,代码如下

public static void main(String[] args) {

    //............. 无关代码...............

    Looper.prepareMainLooper();

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

进入prepareMainLooper()的实现:

public static void prepareMainLooper() {
    prepare(false);//注意这里
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

进来就先执行了一个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));
}

这里我们可以看到,有一个sThreadLocal变量,如果这个变量非空,则执行prepare会报出异常:Only one Looper may be created per thread,如果这个变量为空,我们会为这个变量赋值一个新的Looper对象。
这里的Looper(quiteAllowed)显然是调用了Looper的构造方法:

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

至此我们可以发现,整个过程在app的启动之初,主线程开始调用Looper.prepareMainLooper()方法, 为sThreadLocal设置了一个唯一的Looper,并且这个Looper是持有当前线程的引用的,也就是说,这个Looper与我们调用prepareMainLooper的线程是一个绑定的关系,而这个线程,就是主线程。

因此,在app启动之初,就将主线程所对应的Looper对象放进了sThreadLocal中。

说到这里还需要说一下这个ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。它其实类似于一个HashMap,每个线程对应了一系列数据。我们通过set和get方法读取某个线程所对应的一些数据的值。这个后文会有例子。

回过头继续看刚刚的main入口:

public static void main(String[] args) {

    //............. 无关代码...............

    Looper.prepareMainLooper();

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在为当前线程创建好looper并放进sThreadLocal后,就开始执行Looper.loop()方法。这个方法具体是干什么的呢?

public static void loop() {
    //获得一个 Looper 对象
    final Looper me = myLooper();
    // 拿到 looper 对应的 mQueue 对象
    final MessageQueue queue = me.mQueue;
    //死循环监听(如果没有消息变化,他不会工作的) 不断轮训 queue 中的 Message
    for (;;) {
        // 通过 queue 的 next 方法拿到一个 Message
        Message msg = queue.next(); // might block
        //空判断
        if (msg == null)return;
        //消息分发   
        msg.target.dispatchMessage(msg);
        //回收操作  
        msg.recycleUnchecked();
    }
}

这里就是消息轮询的关键了,在上文中给出的Looper的构造方法中,我们注意到它创建了一个MessageQueue对象mQueue,未来所有的任务都会在这个mQueue队列中存放,主线程通过loop()去遍历这个队列,并依次执行其中的任务。

这里介绍完looper的初始化过程以及系统通过looper实现的消息轮询,下面说一说Handler与这些东西到底有什么关系,先看Handler的构造方法:

public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
//别忘了myLooper的实现
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

这样一看,似乎可以明白了——我们每次创建一个Handler,就会调用myLooper去从sThreadLocal中拿到当前创建Handler的线程的Looper对象,并且根据这个looper对象得到对应的消息队列。也就是说,如果我们在主线程中创建一个handler,那么他就会拿到主线程的Looper,然后得到主线程不断轮询的那个消息队列。

需要注意的是:线程默认是不具有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

当我们使用Handler去发送消息的时候,最终都会走进一个函数:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      msg.target = this;//注意这句 查看定义可以发现 Message的target是一个类型为Handler的成员变量
      //handler遵循谁发送消息 谁处理消息的原则 所以这里把target设置成self

      // 使用默认的 handler 构造方法时,mAsynchronous 为 false。
      if (mAsynchronous) {
          msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);//这句以后,messageQueue就会多一条message
      //looper在调用loop()不断轮训messageQueue的时候就会发现它,并且处理它
    }

看到这里相信能够明白了,我们每次使用handler去发送消息后,都会在当前线程的消息队列中添加一条消息,当前线程轮询到这条消息并执行它。
执行它的过程,刚刚我们在loop()方法的源码中可以看到,有这样一个函数:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//我们没有给上面两个callback赋值,所以走进这个分支
    }
}


//看这个方法的实现:
public void handleMessage(Message msg) {
//这是一个空方法
}

handleMessage方法的方法体是空的,这也是为什么我们在使用的时候常常需要复写一个handleMessage的原因。

至此应该有许多读者已经看晕了,说实话笔者能力有限…这一块正向的去解释确实有点凌乱。因此在这里再总结一下:

在android中线程是默认没有Looper的,在程序启动之初,会为主线程创建一个Looper对象,并且根据Looper初始化了当前线程的消息队列,然后开始主动轮询这个队列中的消息。当我们在某个线程中创建Handler时,就会获取当前线程的Looper(注意,这个线程是创建Handler的线程,而不是handler发送消息的线程),根据looper拿到这个线程的消息队列,然后未来所有通过Handler发送的消息,都会被加入到这个线程的队列中。我们常在主线程中创建了Handler后,用一个新的线程去调用handler.sendmessage()方法,而handler又是遵循自己发送自己处理的机制,会自己接收到消息并走入handleMessage方法,我们再通过复写handleMessage来刷新UI。


以上是对Handler机制以及其运行过程正向的分析,功力有限可能难以做到深入浅出。

有理解的不对的还望指正!感谢阅读!