android 二维码解析原理_Android事件循环原理解析1

本文基于aosp master 分支最新源码说明(什么意思?就是文章发出时的最新源码)

阅读前提:

- 知道Android UI线程的作用(以下简称UI线程/主线程)

知道如何使用android.os.Handler对象通知UI线程

- 知道简单的数据结构,例如 链表,顺序表等等

为什么需要事件循环?

当用户触发一个事件时,应用程序根据程序员的编码设计会执行某些操作来响应用户触发的事件. 比如用户点击一个按钮后,会触发点击事件,执行的操作是将文本框显示的数字增加到某个值,或者用户点击这个按钮会导致应用程序从网络下载某个文件到本地, 或者手机的无线信号断开了, 手机操作系统识别/探测到这个网络变化并将这个变化转化为消息通知到用户的应用程序. 以上的例子中,如果我们的程序中没有一个不断工作的处理从别处传递而来的消息的工人,我们的程序就不可能去响应这些操作,为了响应这些从其他地方传递而来的消息,并且使这些消息能够及时的处理,我们需要有个勤奋努力的工人(线程),它专门负责接受处理消息并且它需要足够快的处理消息,使得我们连续传递消息给这个工人的过程中能够不间断的获得它的反馈,这种时时刻刻都能获得反馈的结果让我们判断出这个工人/应用程序是响应式, 下面将会使用线程 这个更加专业的术语来代指工人并描述其工作过程.

持续工作的工人与随时响应事件的应用

一个工人首先必须得是合格的,意味着发送给这个工人的消息都能够被这个工人依次执行不会错乱顺序或者遗失消息,同时一个优秀的工人能够及时的响应每一条消息,让它的客户能够及时的得到反馈,还有一个聪明的工人能够以最少的资源来完成前述的工作过程,不浪费是非常好的品质. 此三点是Android实现消息循环进而实现可响应应用程序的基础.即不断工作的线程 能够依次不乱序不丢失的且能够及时的处理每一条消息,而且这个线程使用最少的cpu资源来执行这些操作,最少的cpu资源是什么意思? 我们考虑这样两种不同的实现,第一种 线程循环的的处理消息,如果没有消息了那么它依然持续轮训消息源,直到有消息来了继续处理,第二种 线程循环处理消息,如果没有消息了,它就自己挂起,停止对cpu的占用,直到有消息到来重新进入运行. 很明显

相对来说第二种方式 是资源更优的,因为它节约的cpu的利用,在挂起期间cpu会被其他的程序使用. 前面所说的最少的资源其意义就在这里,更少的cpu占用也意味着更少的能量消耗,更加节约电池续航. 同时第二种模式也保障的消息的及时响应,线程循环就保障的只要有消息就会立刻处理, 那么如何保障依次执行不会乱序或者遗失消息, 以及外部是如何将消息投送到循环线程了,聪明的你一定能想到 需要一个存储点来缓存这些消息, 循环工作线程也从这个存储点不断的读取消息来处理,考虑的依次执行的特点,这个存储点的逻辑结构肯定是有序的,同时外部能够方便的随时及时的插入消息, 那么插入操作的时间复杂度就不能特别高,最好是常量时间, 回想我们在数据结构课上学习的一种结构正好符合这种场景,对就是 有序链表, 所以总结下我们思考出来的方案,  外部 通过将消息投入到一个有序链表, 工作线程不断的从有序链表提取顺序最前的消息来处理, 一旦有序链表处理完毕也就是它变空了, 工作/循环线程就挂起等待外部重新填入消息到有序链表中工作线程再恢复执行. 有序链表本身也正好保障了”依次执行不会错乱顺序或者遗失消息“这个特质.

Android的消息循环实现

有了前面的理论基础, 接下来我们看看android是如何按照理论实现android版本的消息循环线程的, 首先我们看看Android如何将一个普通的线程转变消息循环线程

class LooperThread extends Thread {        public Handler mHandler;          public void run() {            Looper.prepare();  // 1              mHandler = new Handler() {                public void handleMessage(Message msg) {                    // 处理 发送而来的消息 msg                }            }; // 2              Looper.loop(); // 3        }}

将一个线程转变成循环线程,在android中是非常方便的,不需要我们自己去实现线程循环,有序链表, 线程的挂起与恢复过程, android已经提供系统库来帮助我们快速实现这个过程, 上述代码相信读者都看了无数次了, 重点就三行代码 我已经编号为1 ,2, 3了. 首先是配置阶段, 然后是 创建一个外部用以通知循环线程的对象mHandler, 最后是开始循环 调用loop(). 接下来我们深入这三行代码

Looper.prepare()揭秘

// sThreadLocal.get() will return null unless you've called prepare().@UnsupportedAppUsagestatic final ThreadLocal sThreadLocal = new ThreadLocal();/** Initialize the current thread as a looper.  * This gives you a chance to create handlers that then reference  * this looper, before actually starting the loop. Be sure to call  * {@link #loop()} after calling this method, and end it by calling  * {@link #quit()}.  */public static void prepare() {    prepare(true);}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));}

我们可以看到 静态方法prepare() 调用 私有静态方法prepare(boolean), 而这个方法工作内容很简单,就是判断 一个静态ThreadLocal变量是否已经存储过一个

Looper对象,如果已经存储了就抛出运行时异常, 否则就创建一个Looper实例并添加到ThreadLocal中,关于ThreadLocal 这里简单的解释下, ThreadLocal的作用就和它的名称所体现的一样,是一个用来存储线程本地变量的容器,它的方法

get()和set() 会为当前调用这两个方法的线程存储专属于这个线程的变量,比如

这里的作用就是会调用set()方法的线程存储一个只有该线程可以获取的Looper实例, 通过这段代码,保障了 每一个线程最多持有(通过ThreadLocal持有)只有一个Looper, 如果多次prepare调用会导致抛出异常.

接下来我们继续深入看看 new Looper(true) 做了什么, 提醒大家牢记文章最开头的理论过程.

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

注意 这里quitAllowed 的值是true, 看看实例化Looper究竟做了什么特别的事情呢? 我们看到它只做了两件事情,第一 创建了一个MessageQueue, 然后将当前线程引用给持有下来, 分别作为成员变量 mQueue与 mThread, 我们在前面的理论论述中应说明了需要一个存储结构来存储待处理的消息, 这里很明显就是这个MessageQueue 对象,我们可以预测 这个MessageQueue就是我们前面所说的有序链表用来接收缓存外部传递的消息,同时Looper对象会从其中提取消息来处理,也就是说这个MessageQueue对象至少会提供两组功能, 一组是放入消息,另一组是提取消息.

我们来仔细看看MessageQueue是如何实现我们的有序链表的

// True if the message queue can be quit.@UnsupportedAppUsageprivate final boolean mQuitAllowed;@UnsupportedAppUsage@SuppressWarnings("unused")private long mPtr; // used by native codeprivate native static long nativeInit();@UnsupportedAppUsage Message mMessages; MessageQueue(boolean quitAllowed) {        mQuitAllowed = quitAllowed;        mPtr = nativeInit(); }

看了上面的代码后, 可能会有些让人搞不清楚, 链表在哪里呢? 构造方法里操作也很简单, 首先把传递来的true存起来,然后调用jni方法获取一个native层的long值, 这个long值有什么作用我们在后面讨论,我们先聚焦有序链表究竟是如何实现的, 上下看了个遍, 敏感的你可能已经发现了mMessages成员变量就是实现有序链表的关键点, 这里我们还要回想基础知识, 还记得如何实现一个有序链表吗? 首先需要一个表头, 然后必须有节点结构, 节点存储了节点本身的值,以及指向下一节点的指针, 思路清晰了吧, 所以这里的mMessages实际上就是表头, 而这个表头的对象类型Message就是链表的节点类型, Message内部必然有至少两个属性,一个是Message本身的值,另一个就是指向下一个Message的指针.

我们这就看看Message源码

public final class Message implements Parcelable {  public int what;  public int arg1;  public Object obj;  public Messenger replyTo;  int flags;  /**     * The targeted delivery time of this message. The time-base is     * {@link SystemClock#uptimeMillis}.     * @hide Only for use within the tests.     */    @UnsupportedAppUsage    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)    public long when;   /*package*/ Bundle data;    @UnsupportedAppUsage    /*package*/ Handler target;    @UnsupportedAppUsage    /*package*/ Runnable callback;    // sometimes we store linked lists of these things    @UnsupportedAppUsage    /*package*/ Message next;  private static Message sPool;  public static final Object sPoolSync = new Object();  private static int sPoolSize = 0;  private static final int MAX_POOL_SIZE = 50; public Message() {}  }  

看了Message对象突然整个人就炸了fa0213361afe25bcb1fe683ff0a7c955.pngfdebec52c5894dd44bc3fc0913f79f84.png不是说好的两个属性吗? 人与人之间的信任呢cf98a2bf092c78d1c2f79deaa3d283af.png?

各位老板且慢急躁.

我们细细看, 前面我们说到有序链表的节点有两个属性,一个是值,另一个是下一节点,实际上我们在代码里可以直接找到的就是 next属性,它就是下一节点的指针, 那么值是什么呢? 其实值不一定只有一个字段属性, 有时候业务需求增加会导致需要多个字段来表征值,典型的就是有序链表必须有一个字段来表示顺序,事件循环的消息要进行排序依据什么,当然是消息的时间了,什么时间呢? 当然是消息应该被处理的时间,这个时间就是Message对象public long when 这个字段了, 所有的消息按照when的大小(也就是消息发生的先后顺序)在链表中从前到后排列,when大的就排队在链表后面,when小的就排队在前面, 循环提取Message总是提取链表中的when值最小的消息优先处理. 初此外的其他成员变量都是为了满足android特定业务而增加的值字段, 典型的就是what, arg1, arg2, object, callback, 他们分别用于不同场景下持有待处理的数据, 可能只需要用到其中一个字段,也可能需要多个字段配合,之所以这么多属性来表示节点值 完全是因为android消息循环的使用场景比较丰富,需要这些字段来配合完成这些场景的消息值的存储.

这里提一下sPool这个静态变量, 我们前面我们已经说了 好的工人 用最少的资源完成工作, 这个sPool作用就在这里,外部使用Message对象为载体将消息传递给MessageQueue, 如果不做特别处理, 外部自己实例化Message对象,那么有多少消息就会创建多少Message对象,而Android中Message对象的传递是贯穿应用程整个生命周期的,如此多数量的对象创建,处理完后就丢弃会造成大量的GC,影响程序的内存使用,甚至可能消耗玩应用可使用的内存导致OOM异常

于是这里就采取了对象池的思想,同样用一个链表来缓存处理完毕的Message对象,  同时提供了obtain 方法来获取缓存池中的对象,以及recycle方法来回收使用完毕的Message对象,这里不再深入研究,我们把注意力集中在消息循环机制上.

----

这里我们总结一下:

 消息循环需要一个有序链表作为沟通外部与消息循环线程的桥梁, MessageQueue实际就表示了这个有序链表,其中的mMessages成员就是有序链表的表头, 其类型是Message,表头Message内有多个值属性共同完成不同场景下的消息内容的存储, 同时有一个when字段表示消息应该被处理的时间,这个字段也是有序链表的顺序排序的依据, 还有一个next字段指向了下一个节点,同样是Message对象.

各位老板 今天就到这里 我们下期再见.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值