Art of Android Development Reading Notes 10

《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

第10章 Android的消息机制

10.1 Android消息机制概述

(1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueueLooper的支撑。MessageQueue是以单链表的数据结构存储消息列表但是以队列的形式对外提供插入和删除消息操作的消息队列。MessageQueue只是消息的存储单元,而Looper则是以无限循环的形式去查找是否有新消息,如果有的话就去处理消息,否则就一直等待着。 (2)Handler的主要作用是将一个任务切换到某个指定的线程中去执行。 为什么要提供这个功能呢? Android规定UI操作只能在主线程中进行,ViewRootImplcheckThread方法会验证当前线程是否可以进行UI操作。 为什么不允许子线程访问UI呢? 这是因为UI组件不是线程安全的,如果在多线程中并发访问可能会导致UI组件处于不可预期的状态。另外,如果对UI组件的访问进行加锁机制的话又会降低UI访问的效率,所以还是采用单线程模型来处理UI事件。 (3)Handler的创建会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程中不存在Looper的话就会报错。Handler可以用post方法将一个Runnable投递到消息队列中,也可以用send方法发送一个消息投递到消息队列中,其实post最终还是调用了send方法。

10.2 Android的消息机制分析

(1)ThreadLocal的工作原理 1.ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑使用ThreadLocal。 对于Handler来说,它需要获取当前线程的Looper,而Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以实现Looper在线程中的存取了。 2.ThreadLocal的原理:不同线程访问同一个ThreadLocal的get方法时,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同线程中维护一套数据的副本并且彼此互不干扰。 3.ThreadLocal是一个泛型类public class ThreadLocal<T>,下面是它的set方法

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

Values是Thread类内部专门用来存储线程的ThreadLocal数据的,它内部有一个数组private Object[] table,ThreadLocal的值就存在这个table数组中。如果values的值为null,那么就需要对其进行初始化然后再将ThreadLocal的值进行存储。 ThreadLocal数据的存储规则:ThreadLocal的值在table数组中的存储位置总是ThreadLocal的索引+1的位置。

(2)MessageQueue的工作原理 1.MessageQueue其实是通过单链表来维护消息列表的,它包含两个主要操作enqueueMessagenext,前者是插入消息,后者是取出一条消息并移除。 2.next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将它从链表中移除。

(3)Looper的工作原理 1.为一个线程创建Looper的方法,代码如下所示

new Thread("test"){
    @Override
    public void run() {
        Looper.prepare();//创建looper
        Handler handler = new Handler();//可以创建handler了
        Looper.loop();//开始looper循环
    }
}.start();

2.Looper的prepareMainLooper方法主要是给主线程也就是ActivityThread创建Looper使用的,本质也是调用了prepare方法。 3.Looper的quitquitSafely方法的区别是:前者会直接退出Looper,后者只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper退出之后,通过Handler发送的消息就会失败,这个时候Handler的send方法会返回false。 在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。 4.Looper的loop方法会调用MessageQueuenext方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞着在那里,这也导致了loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),其中的msg.target就是发送这条消息的Handler对象。

(4)Handler的工作原理 1.Handler就是处理消息的发送和接收之后的处理; 2.Handler处理消息的过程

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);//当message是runnable的情况,也就是Handler的post方法传递的参数,这种情况下直接执行runnable的run方法
    } else {
        if (mCallback != null) {//如果创建Handler的时候是给Handler设置了Callback接口的实现,那么此时调用该实现的handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//如果是派生Handler的子类,就要重写handleMessage方法,那么此时就是调用子类实现的handleMessage方法
    }
}

private static void handleCallback(Message message) {
        message.callback.run();
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

3.Handler还有一个特殊的构造方法,它可以通过特定的Looper来创建Handler。

public Handler(Looper looper){
  this(looper, null, false);
}

4.Android的主线程就是ActivityThread,主线程的入口方法就是main,其中调用了Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()方法来开启主线程的消息循环。主线程内有一个Handler,即ActivityThread.H,它定义了一组消息类型,主要包含了四大组件的启动和停止等过程,例如LAUNCH_ACTIVITY等。 ActivityThread通过ApplicationThreadAMS进行进程间通信,AMS以进程间通信的方法完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

OK,本章结束,谢谢阅读。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏林冠宏的技术文章

XGoServer 一个基础性、模块完整且安全可靠的服务端框架

作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:htt...

397180
来自专栏余林丰

备忘录模式

备忘录模式,望文生义就知道它是用来做备忘的,或者可以直接说是“备份”。当需要保存当前状态,以便在不久要恢复此状态时,就可以使用“备忘录模式”。将当前”状态“备份...

20270
来自专栏SDNLAB

源码解读ODL的MAC地址学习(二)

1 简介 上一篇文章(源码解读ODL的MAC地址学习(一))已经分析了MAC地址学习中的ARP请求的部分源码,下面将接着上一篇文章,介绍一下ARP响应和生成流表...

44050
来自专栏Kirito的技术分享

深入理解RPC之传输篇

RPC 被称为“远程过程调用”,表明了一个方法调用会跨越网络,跨越进程,所以传输层是不可或缺的。一说到网络传输,一堆名词就蹦了出来:TCP、UDP、HTTP,同...

1.1K70
来自专栏coder修行路

Go 源码学习之--net/http

其实自己不是很会看源码,但是学习优秀的源码是提升自己代码能力的一种方式,也可以对自己以后写代码有一个很好的影响,所以决定在之后的时间内,要有一个很好的习惯,阅读...

78450
来自专栏数值分析与有限元编程

Fortran知识 | 输出结果出现NaN

一旦输出结果出现NaN,编译器不会给出任何错误提示,这个时候该如何调试程序呢? ? 点击菜单栏的调试,最后一个为XXXX属性,打开对话框,左侧展开 Fortra...

39570
来自专栏Coding迪斯尼

从0到1用java再造tcpip协议栈:实现ARP协议层

经过前两节的准备,我们完成了数据链路层,已经具备了数据包接收和发送的基础设施,本机我们在此基础上实现上层协议,我们首先从实现ARP协议开始。先简单认识一下ARP...

22930
来自专栏小樱的经验随笔

一文让你完全弄懂Stegosaurus

国内关于 Stegosaurus 的介绍少之又少,一般只是单纯的工具使用的讲解之类的,并且本人在学习过程中也是遇到了很多的问题,基于此种情况下写下此文,也是为我...

11220
来自专栏恰童鞋骚年

自己动手写工具:百度图片批量下载器

开篇:在某些场景下,我们想要对百度图片搜出来的东东进行保存,但是一个一个得下载保存不仅耗时而且费劲,有木有一种方法能够简化我们的工作量呢,让我们在离线模式下也能...

48910
来自专栏大内老A

[WCF的Binding模型]之三:信道监听器(Channel Listener)

信道管理器是信道的创建者,一般来说信道栈的中每个信道对应着一个信道管理器。基于不同的消息处理的功能,将我们需要将相应的信道按照一定的顺序能组织起来构成一个信道栈...

21050

扫码关注云+社区

领取腾讯云代金券