Android消息机制

  • Post author:
  • Post category:其他




Handler

Handler作为Android消息机制的上层接口,这使得开发过程中只需要和Handler交互即可。

Handler的使用过程也很简单,通过它可以轻松将一个任务切换到Handler所在线程中去执行,更新UI是Handler的一个特殊的使用场景,有时需要在子线程上进行耗时的I/O操作,操作完成后需要在UI上做一些改变,此时则需要通过Handler将更新UI的操作切换到主线程中执行。



消息机制简述

android消息机制主要指的是Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。



MessageQueue

消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除的工作,但内部存储结构并非真正的队列,而是采用单链表的数据结构来存储消息列表。



Looper

消息循环,由于MessageQueue只是消息存储单元,无法处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式,去查找是否有新消息,有的话就处理消息,否则一直等待。



ThreadLocal

ThreadLocal并不是线程,作用是可以在每个线程中互不干扰地存储并提供数据,可通过ThreadLocal轻松获取每个线程的Looper。而线程默认是没有Looper,如果要是使用Handler就必须为线程创建Looper,而ActivityThread被创建时会默认初始化Looper,因此可在主线程中使用Handler。



对Android消息机制的理解

Android消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三者实际上是一个整体,只是开发过程中较多地接触到Handler而已。Handler的主要作用是将一个任务切换到某个指定的线程中去执行。ViewRootImpl的checkThread会对UI操作进行验证,子线程访问UI会导致异常抛出。

而在主线程中进行耗时操作容易导致程序无法响应即ANR,因此系统提供Handler用于解决子线程无法访问UI的问题。

如果系统允许子线程访问UI怎么办?Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,加锁会导致逻辑复杂且UI访问效率低。



Handler的工作原理

Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,若当前线程没有Looper就会报错,可通过创建Looper或在一个有Looper的线程中创建Handler也行。

当Handler创建完毕后,这时其内部的Looper以及MessageQueue可进行协同工作,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,或是通过Handler的send方法发送一个消息,这个消息同样会在Looper中去处理。

其实post方法最终也是通过send方法来完成。



Send方法工作过程

当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法,将这个消息放入消息队列中,然后Looper发现有新消息到来时就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage就会调用。Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就会被切换到创建Handler所在的线程中去执行了。



分析Android消息机制



ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后只有指定线程中可以获取到存储数据,常用于某些数据是以线程为作用域并且不同线程具有不同的数据副本时可以考虑ThreadLocal,如Looper、ActivityThread以及AMS中都用到了ThreadLocal。由于Looper的作用域就是线程且不同线程具有不同的Looper,这是ThreadLocal就可以轻松实现Looper在线程中的存取,不然就得使用全局的哈希表供Handler查找指定线程的Looper了。

ThreadLocal另一个使用场景是复杂逻辑下的对象传递,如监听器的传递,有时一个线程中的任务过于复杂,这可能表现为函数调用栈比较深,以及代码入口的多样性,这时如果需要监听器能够贯穿整个线程的执行过程,ThreadLocal可以让监听器作为线程内的全局对象而存在,线程内部只需要通过get方法就可以获取到监听器,如果不采用只能是在函数调用中传递监听器或者是监听器作为静态变量供线程访问,前者当函数调用栈很深时,通过函数参数来传递监听器对象几乎是不可接受的,后者则不具备扩充性。

不同线程访问同一个ThreadLocal对象,它们通过ThreadLocal获取到的值不一样。



ThreadLocal的内部实现

public classThreadLocal<T>

Thread类的内部有一个成员专门用于存储线程的ThreadLocal的数据:

ThreadLocal.Values localValues,若localValues的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。



ThreadLocal的set

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



ThreadLocal的put

LocalValues内部有一个数组:private Object[] table,ThreadLocal的值就存在其中。

在这里插入图片描述

存储规则:ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置。



ThreadLocal的get

当value值不为空则取出table数组,并找出reference对象在table数组中的位置,该位置的下一个位置即为对应值,为空则返回初始值。

在这里插入图片描述

ThreadLocal的set和get针对的对象都是当前线程的localValues对象的table数组,因此不同线程对同一个ThreadLocal对象操作数据,互不干扰。



消息队列的工作原理

消息队列在android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next作用是从消息队列中取出一条消息,并将其从消息队列中移除。

再次申明:消息队列实际上是一个单链表。

enqueueMessage主要是单链表的插入操作。

next提供一个无限循环来不断读取新消息,读出新消息并将其从单链表中删除,否则一直阻塞。



Looper的工作原理

Looper在android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有消息,有新消息就立刻处理,否则一直阻塞在那里。



Looper的创建

Looper的构造方法中创建一个MessageQueue即消息队列,然后将当前线程的对象保存起来。

Looper通过Looper.prepare即可为当前线程创建一个Looper,接着通过Looper.loop来开启消息循环。

Looper除了prepare方法外,还提供了prepareMainLooper方法,它主要是给主线程也就会ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。

由于主线程的Looper比较特殊,所以Looper提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。



Looper的退出

Looper可通过quit和quitSafely来退出一个Looper,区别在于前者是直接退出Looper,而后者只是设定一个退出标志,然后把消息队列中的已有消息处理完毕后才安全地退出。

Looper退出后,通过handler发送的消息就会失败,send方法返回false。

如果为子线程手动创建了Looper,则应该在所有事情完成以后调用quit方法来终止消息循环,否则这个子线程就一直处于等待状态,当其退出Looper以后,线程就会立刻终止,因此建议不需要时终止Looper。



Looper的loop

死循环,除非MessageQueue的next返回为null,当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列必须退出,当消息队列被标为退出状态时,它的next方法就会返回null。也就是说Looper必须退出。

当没有消息时,next一直堵塞,loop一直堵塞,当next 方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样,Handler发送的消息最终会交给它的dispatchMessage方法来处理,但不同的是dispatchMessage是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定线程中去执行了。



Handler的工作原理

Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post和send的一系列方法来实现,post最终也是通过send实现的

发送一条消息的流程如下

在这里插入图片描述

在这里插入图片描述

最终还是会调用enqueueMessage向MessageQueue插入消息,looper收到消息后最后会将其交给handler处理,即调用Handler.dispatchMessage

在这里插入图片描述

在这里插入图片描述

流程解析:

先检查Message的callback是否为null,不为null则通过handleCallback来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法中传递的Runnable参数,而handleCallback就是执行其run方法。

其次则是检查mCallback是否为null,不为null就通过mCallback的handleMessage方法来处理消息。

最终通过handleMessage处理消息。

Callback是个接口,定义如下

public interface Callback{
   public boolean handleMessage(Message msg);
}

通过Callback可以这样创建Handler对象:Handler handler = new Handler(callback)。Callback使得我们可以创建一个Handler的实例但不需要派生Handler的子类。而日常开发中,创建Handler最常见的就是派生一个Handler的子类并重写其handleMessage方法来处理具体的消息。



主线程的消息循环

Android的主线程为ActivityThread,主线程的入口方法为main,在main中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

在这里插入图片描述

在这里插入图片描述

当主线程的消息循环开始了之后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,其内部定义了一组消息类型,包含四大组件的启动和停止等过程。

在这里插入图片描述

ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。



版权声明:本文为weixin_51109304原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。