第十章 Android的消息机制

本文深入解析Android中的消息机制,包括Handler、Looper、MessageQueue的工作原理及其如何协作完成UI更新任务。探讨了为何需要在特定线程中更新UI,并介绍了ThreadLocal在其中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 有时候需要在子线程中进行耗时的I/O操作,可能是读取文件或者是访问网络等,当耗时操作完成以后可能需要在UI上做一些改变,这时候就需要用Handler切换到主线程执行。
  2. MessageQueue 只是一个消息存储单元,不能处理消息,内部的存储结构是单链表。
  3. Looper 是消息循环,以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等着。
  4. ThreadLocal 可以在每个线程中存储数据,Handler创建的时候会采用当前的Looper来构造消息循环系统,那么Handler是怎么获取到当前线程的Looper呢?
    使用ThreadLocal,ThreadLocal在不同的线程中互不干扰地存储并提供数据,可以轻松获取每个线程的Looper。线程默认是没有Looper的,如果使用Handler就必须创建Looper。UI线程中在ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

10.1 Android 的消息机制概述

Android 规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。ViewRootImpl 对UI操作做了验证:

    /**
     * ViewRootImpl对UI操作做了验证,验证工作由该方法进行
     */
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

提供Handler,主要是为了解决在子线程中无法访问UI的矛盾。
为什么不允许在子线程中访问UI呢?
因为 Android 的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预估的状态。
为什么系统不对UI控件的访问加上锁机制呢?
缺点有两个:1:加上锁机制会让UI访问的逻辑变得复杂;2:锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
Handler 创建时会采用当前线程的 Looper 来构建内部的消息循环系统,如果当前线程没有Looper,会报错。Looper是运行在床架Handler所在的线程中的。

10.2.1 ThreadLocal 的工作原理

  1. ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说无法获取到数据。
  2. 当某些数据时以线程为作用域并且不同的线程具有不同的数据副本的时候,就可以考虑使用ThreadLocal。
  3. 对于Handler来说,它需要获取当前线程的 Looper,Looper 的作用域就是线程并且不同线程具有不同的 Looper,此时通过 ThreadLocal 就可以实现 Looper 在线程的存取。如果不采用 ThreadLocal,那么系统就必须提供一个全局的哈希表供 Handler 查找指定线程的 Looper,但是系统并没有这么做,而是选择了ThreadLocal,这就是ThreadLocal的好处。
  4. ThreadLocal 的另一个使用场景:复杂逻辑下的对象传递,比如监听器的传递,采用 ThreadLocal 可以让监听器作为线程内的全局对象而存在,在线程内部只需要 get() 就可以获取到监听器。如果不采用 ThreadLocal,可以用两种方法做到:
    (1):将监听器通过参数的形式在函数调用栈中进行传递。局限性:当函数调用栈很深的时候,通过函数参数来传递对象这几乎是不可能的,会让程序设计看起来很糟糕
    (2):将监听器作为静态变量供线程访问。局限性:如果同时有两个线程在执行,需要提供两个静态的监听器对象,如果有10个线程在并发执行呢,提供10个静态的监听器对象,显然是不可思议的。
  5. 为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰?
    因为不同线程访问ThreadLocal的get(),ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。不同线程的数组是不一样的。

10.2.2 消息队列的工作原理

MessageQueue:通过单链表的数据结构来维护消息列表
enqueueMessage():往消息队列中插入一条消息
next():从消息队列中取出一条消息并将其从消息队列中移除,next() 是一个无线循环的方法,如果消息队列中没有消息,那么 next() 方法会一直阻塞在这里,当有新消息到来,next会返回这条消息并将其从单链表中删除。

10.2.3 Looper的工作原理

Looper:消息循环
构造方法:创建MessageQueue对象,并把当前线程的对象保存起来。
prepare():为当前线程创建一个Looper,并存到ThreadLocal中。
quit():直接退出Looper
quitSafely():设定一个退出标记,消息队列中的消息处理完毕后才安全退出。
loop():是一个死循环,唯一跳出循环的方式就是MessageQueue.next()返回了null。
(1). 当Looper.quit或者quitSafely被调用的时候,MessageQueue的quit方法会被调用来通知消息队列退出,当消息队列被标记为退出状态时,它的next()就会返回null。:Looper必须退出,否则loop方法就会无限循环下去。
(2). looper 会调用MessageQueue.next()获取新消息,而next是一个阻塞操作,当没有消息的时候,next方法就会一直阻塞在哪里,导致looper也会一直阻塞在哪里。
(3). 如果MessageQueue.next()返回了消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg)(msg.target 就是发送消息的 Handler 对象)。然后会根据情况调用Handler d的run()/handMessage()。成功将代码逻辑切换到指定的线程中执行。
注意:
Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。在子线程中如果手动为其创建了Looper,那么在所有事情完成以后应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态。。而退出Looper以后,该线程就会立刻终止,因此建议不需要的时候终止Looper。

10.2.4 Handler 的工作原理

post/send:Handler 发送消息的过程就是向 MessageQueue 中插入一条消息 —> MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 收到消息后就调用 Handler 的dispatchMessage(msg)分发消息,这时候消息就交给了 Handler 处理。
构造方法:如果当前线程没有Looper的话,就会抛出异常。这也解释了在没有Looper的子线程中创建Handler会引发程序异常的原因了。

        mLooper = Looper.myLooper();
        /**
         * 在创建Handler的时候,没有Looper会引发程序异常
         */
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

这里写图片描述
10.3 主线程的消息循环
1. 主线程的入口方法是 ActivityThread 的 main 方法,main方法中Looper.prepareMainLooper()创建主线程的 Looper 以及MessageQueue,并通过 Looper.loop()来开启主线程的消息循环。
2. 主线程的消息循环开始之后,就需要一个 Handler 来和消息队列进行交互,这个Handler 就是ActivityThread.H,内部定义一组消息类型,包括四大组件的启动和停止等。
3. 主线程的消息循环模型: ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成ActivityThread的请求后会回调 ApplicationThread 的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值