《Android开发艺术探索》学习笔记之Android的消息机制
一、概述
-
1、Handler的主要作用是将某个任务切换到指定的线程中去执行
- eg:子线程中无法更新UI,需切换到主线程
- ViewRootImpl通过 checkThread() 方法检查更新UI操作是否是在主线程当中
- 原因:Android的UI是线程不安全的,存在并发访问的问题。加锁也不合适
- 加锁会让UI访问的逻辑变得复杂
- 加锁会降低UI访问的效率,因为锁会阻塞某些线程的执行
- eg:子线程中无法更新UI,需切换到主线程
-
2、消息机制各部分之间的联系:
- Thread是最基础的,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上
-
Handler是Android消息机制的上层接口
- Android消息机制主要是指Handler的运行机制
- 其运行需要底层的MessageQueue和Looper的支撑
-
3、Handler的工作原理:
- (1)每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列
- 在执行new Handler()的时候,默认情况下Handler会绑定当前代码执行的线程
- (2)创建时会采用当前线程的Looper来构建内部的消息循环系统
- 如果当前线程没有Looper,系统会报错
- ActivityThread(即主线程)被创建时就会初始化Looper,所以默认可使用Handler
- 非主线程默认是没有Looper的,使用时需要去创建
- Handler初始化之前,Looper的初始化必须先完成
- (3)总结:Handler - MessageQuene和Looper - Thread 是在每个线程都一一对应的
- (1)每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列
-
4、消息机制大致的工作过程:
- 首先Handler通过Handler的send方法发送一个消息(或Handler的post方法发送一个Runnable)到Handler内部的Looper当中处理
- post方法最终也是通过调用send方法
- 当Handler的send方法被调用的时候,它会调用MessageQueue的enqueueMessage方法将这个消息放到消息队列里,当Looper发现有新消息到来时,就会处理这个消息
- 最终消息中的Runnable或Handler的handleMessage方法会被调用。
- Looper是运行在创建Handler所在的线程中的,这样Handler中(在Thread 1中调用Thread2中创建的Handler)的业务逻辑就被切换到创建Handler的线程(Thread 2)中执行了
- 首先Handler通过Handler的send方法发送一个消息(或Handler的post方法发送一个Runnable)到Handler内部的Looper当中处理
二、消息机制各大组成部分详解
1、ThreadLocal
-
定义:ThreadLocal是一个线程内部的数据存储类。
- 通过它可以在指定线程中存储数据
- 只有在指定线程中才可以获取到存储的数据
-
使用场景:
- (1)一般来说,当某些数据(eg:同一个对象)是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
- 如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper
- (2)复杂逻辑下的对象传递。
- eg:监听器的传递:通过ThreadLocal让监听器作为线程内的全局对象,在线程内只要通过get方法就可以获取到监听器
- (1)一般来说,当某些数据(eg:同一个对象)是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
-
使用:
- ThreadLocal是一个泛型类
ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
- 在不同的线程中可以调用这同一个ThreadLocal对象的set方法去设置相应的value
-
原理:不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前的ThreadLocal的索引去查找出对应的value值
- 不同线程中的数组是不同的,ThreadLocal的set和get方法所操作的对象都是当前线程的LocalValues对象的table数组
- set方法解析
-
在Thread类中有一个成员专门用于存储线程的ThreadLocal的数据:
ThreadLoacl.Values localValues -
localValues中有一个object[] table用于存放ThreadLocal在每个线程中的数据,调用put方法存入
- ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置
table[index] = key.reference; table[index + 1] = value;
-
ThreadLocal对象set方法源码
public void set(T value){ ThreadLocal currentThread = Thread.currentThread(); //获取到当前线程对象 Values values = values(currentThread); //获取到当前线程下的values对象 if(values == null){ //如果values数组为null,则对他初始化 values = initializeValues(currentThread); } values.put(this, value); //将values中的ThreadLocal数据 //存入Object[] table数组当中 }
-
2、Message
-
获取Message
- (1)Message msg = new Message();
- 不建议使用直接构造
- (2)Message msg = Message.obtain(handler);
- (3)Message msg = handler.obtainMessage();
- (1)Message msg = new Message();
-
向Message中装填数据及取出
-
(1)msg.what 设置识别码
- 插入:
msg.obj = 1
- 取出:
- what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别出不同的Message,以便我们做出不同的处理操作
- 插入:
-
(2)msg.arg1/2 设置简单值
- 插入:
msg.arg1 = 123; msg.arg2 = 321;
- 取出:
msg.getArg1/2();
- 插入:
-
(3)msg.obj 传入对象
- 插入:
//可以通过给obj赋值Object类型传递向Message传入任意数据 msg.obj = null;
- 取出:
- 插入:
-
(4)msg.setData(Bundle bundle)/getData() 通过Bundle传输
- 插入:
//可以通过setData方法向Message中写入Bundle类型的数据 msg.setData(null);
- 取出:
//可以通过和getData方法从Message中读取Bundle类型的数据 Bundle data = msg.getData();
- 插入:
-
3、MessageQueue
-
定义:包含消息的插入(enqueueMessage)和读取(next)两个操作
- 实际的数据结构并不是队列而是单链表
-
使用:不需要直接使用这两个方法,已封装。
- enqueueMessage():插入操作,往消息队列中插入一条消息。
- next:读取操作,从消息队列中去除一条消息并将其从消息队列中移除。
-
源码解析:
- enqueueMessage和next方法中都存在死循环
for(;;) //死循环
4、Looper
-
定义:消息循环。它会不停地从MessageQueue中查看是否有新的消息,如果有新消息就会立刻处理,否则就会一直堵塞在那里
-
构造方法:
- Looper的构造方法是私有的,只能通过工厂方法 Looper.myLooper() 这个静态方法获取当前线程所绑定的Looper
- 因为线程与Looper是一对一绑定的
private Looper(boolean quitAllowed) { // 创建MessageQueue mQueue = new MessageQueue(quitAllowed); // 保存当前线程对象 mThread = Thread.currentThread(); }
- Looper的构造方法是私有的,只能通过工厂方法 Looper.myLooper() 这个静态方法获取当前线程所绑定的Looper
-
保存对当前线程的引用
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
-
使用:
- (1)为一个线程创建Looper
new Thread("Thread#2"){ @Override public void run(){ Looper.prepare(); Handler handler = new Handler(); Looper.loop(); } }.start();
-
Looper.prepare():为当前线程创建一个Looper(将Looper绑定到当前线程)
- Looper.prepareMainLooper:给ActivityThread创建Looper使用,内部调用prepare方法。同时提供 Looper.getMainLooper() 用于在任何地方获取到主线程的Looper
- Looper.loop():开启消息循环
-
Looper.prepare():为当前线程创建一个Looper(将Looper绑定到当前线程)
- (2)退出Looper:
- quit():直接退出Looper
- quitSafely():设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出
- (1)为一个线程创建Looper
-
原理:
//loop方法关键代码 ... for(;;) { Message msg = queue.next(); if (msg == null) { return; //在每次进入循环体的开始首先进行消息的判空 } ... msg.target.dispatchMessage(msg); //mag.target是发送这条消息的Handler对象 //Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的 //这样就实现了切换线程的逻辑 ... } ...
- loop方法是一个死循环,唯一跳出的方法是MessageQueue的next方法返回了null
- 当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit方法或quitSafely方法来通知消息队列退出
- 当消息队列被标记为退出状态时,他的next方法就会返回null
5、Handler
-
定义:主要包含消息的发送和接收过程
- 发送消息源码调用过程:
- 发送消息源码调用过程:
-
使用:
- (1)创建Handler
- 法1:派生一个Handler的子类并重写其handleMessage方法来处理具体消息(最常使用)
- 法2:通过Callback
Handler handler = new Handler(callback);
- Callback是一个接口
public interface Callback { public boolean handleMessage(Message msg); }
- Callback是一个接口
- 法3:通过一个特定的Looper来构造Handler
public Handler(Looper looper) { this(looper, null, false); }
- (2)消息的发送:send的一系列方法(或使用post的一系列方法)
- post最终也会调用send的方法
- 发送消息的过程仅仅是向消息队列中插入了一条消息
- (1)创建Handler
-
dispatchMessage源码解析:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); //处理消息 } }
- (1)首先检查Message的callback是否为null,不为null就通过handleCallback方法处理消息
- Message的Callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable参数
//handleCallback源码 private static void handleCallback(Message message) { message.callback.run(); }
- (2)其次检查mCallback是否为null,不为null就通过mCallback的handleMessage处理消息
- Callback是个接口,源码见 使用-法2
- (3)最后如果前两个都为null才会调用 handleMessage() 方法处理消息
- (1)首先检查Message的callback是否为null,不为null就通过handleCallback方法处理消息