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

在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机制以及其运行过程正向的分析,功力有限可能难以做到深入浅出。

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值