学习内容
- Android 的消息机制
- Handler 即其底层支撑
原文开篇部分:
- 从开发角度来说,Handler 是 Android 消息机制的上层接口,通过它可以将任务切换到 Handler 所在的线程中执行。
- 更新 UI 仅仅是 Handler 的一个特殊的使用场景。本质不是专门用来更新 UI,只是常被用来这么做而已。
- Andorid 的消息机制主要指 Handler 的运行机制,Handler 的运行底层的 MessageQueue 和 Looper 的支撑。
- MessageQueue(消息队列):采用单链表的数据结构存储消息列表,以队列的形式对外提供插入和删除的工作。只负责存储,不管处理。
- Looper 负责处理消息:以无限循环的形式查找是否有新消息,有则处理,无则等待。
- ThreadLocal 可以在把不同的线程中互不干扰的存储并提供数据,通过它来获取每个线程中的 Looper。
- 线程默认没有 Looper,因此如果需要使用 Handler 必须先为线程创建 Looper。UI 线程(ActivityThread)创建时会初始化 Looper,因此默认可以在主线程中使用 Handler。
Android 的消息机制概述
1.Handler 的作用:
- 将一个任务切换到某个制定的线程中执行。
2.为什么提供上述 Handler 的功能?
- Android 规定访问 UI 只能在主线程中执行,如果在子线程中访问 UI,那么程序会抛出异常。
- 以上限制,源码中 ViewRootImpl 对 UI 操作进行验证,通过 chearThread 方法实现。
- 同时由于 Android 的限制,导致必须在主线程中访问 UI,但是如果在主线程中进行耗时操作,可能会导致 ANR,因此。系统提供 Handler,主要原因就是为了解决在子线程中无法访问 UI 的矛盾。(子线程进行耗时操作,通过 Handler 访问 UI)
3. Android 为什么不允许子线程访问 UI?
- 核心点:Andorid 的 UI 控件并非线程安全。
- 通常方法及其问题:
- 对 UI 控件的访问加入*上锁机制
- 问题:使 UI 访问的逻辑变得复杂;锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。
- 解决方案:
- 采用单线程模型来处理 UI 操作
- 通过 Handler 切换 UI 访问的执行线程即可
4.Handler 的工作原理:
- Handler 创建时会采用当前线程的 Looper 来构建内部的消息循环系统,当前线程没有 Looper 会报错
- 创建完毕后,通过 Handler 的 post 方法将 Runnable 投递到 Handler 内部的 Looper 中去处理;或者通过 Handler 的 send 方法发送消息,该消息也会在 Looper 中处理。实际 post 最后也会调用 send 方法。
- send 方法的工作过程:调用 send 方法后,它会调用 MessageQueue 的 enqueueMessage 方法将该消息放入消息队列中,然后 Looper 发现新消息后,会处理这个消息,最终消息中的 Runnable 或者 Handler 的 handleMessage 方法就会被调用。
- 注意:Looper 运行在创建 Handler 所在的线程中,这样一来 Handler 中的业务逻辑就被切换到创建 Handler 所在的线程中去了。
Android 的消息机制分析
ThreadLocal 的工作原理
1.基本定义
- ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只有在制定线程中可以获取到存储的数据,而其他线程则无法获取到数据。
- why ?(为什么通过 ThreadLocal 可以在不同的线程中维护一套数据的副本并且彼此互不打扰?)
- (简单说明) 不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。显然,不同线程中的数组是不同的,这也就是原因所在。
2.使用场景
- 存储以线程为作用于并且不同县城具有不同的数据副本的数据
- 如:通过 ThreadLocal 存储不同线程中的 Looper,供 Handler 获取当前线程的 Looper
- 复杂逻辑下的对象传递,比如监听器的传递。
- 复杂逻辑:比如 函数调用栈比较深以及代码入口的多样性。
- 采用 ThreadLoacl 让监听器作为线程内的全局对象存在,而线程内部只要通过 get 方法就可以获取到监听器。
3.原理
ThreadLocal 是一个泛型类,定义为 public class ThreadLocal<T>,核心在于 ThreadLocal 的 get 和 set 方法。
-
set 方法:
(lz这里查看了一下现版本的源码,发现和书中有些许差异)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
虽说和书中源码有出入,但是大体思想是一致的:通过一个结构 ThreadLocalMap 存储线程的 ThreadLocal 数据。
ThreadLocalMap 部分代码如下:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private Entry[] table; //...
内部的这个 private Entry[] table 数组即用来存储 ThreadLocal 的值。
回到上面 ThreadLocal 的 set 方法,如果ThreadLocalMap 为空,则创建该线程的 ThreadLocalMap 对象,并将 ThreadLocal 的值存储。如果非空,则直接通过 ThreadLocalMap.set 方法存储ThreadLocal 的值,ThreadLocalMap.set 的源码如下:
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
ThreadLocal 的值直接存储在 Entry.key 匹配的 Entry.value 中。(和书中有所差异,并非凭借索引位置)
-
set 方法介绍完,下面介绍 get 方法:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
get 方法实际就获取 ThreadLocalMap 对象,如果非空,则调用 ThreadLocalMap.getEntry 方法,得到目标 Entry 对象,该 Entry 对象的 value 字段即为 ThreadLocal 值。如果 ThreadLocalMap 为空,那么返回初始值,该初始值可通过重写 initialValue 方法更改。
ThreadLocalMap.getEntry 方法如下:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
可以看到,逻辑很清晰,取出 table 数组,并找出 ThreadLocal 的引用对象所在的 Entry ,返回之。
-
小结
- 操作对象是当前线程的 ThreadLocalMap 内的 private Entry[] table 数组,读写仅限于各自线程的内部,
- 因此多个线程可以互不干扰的存储和修改数据。
消息队列的工作原理
1.基本介绍
- 消息队列在 Android 中指的是 MessageQueue,内部通过一个单链表的数据结构来维护消息列表(方便插入和删除)
2. 原理
- enqueueMessage 方法:向消息队列中插入一条消息。
- next 方法:无限循环的方法。从消息队列中取出一条消息并将其从消息队列中移除。如果消息队列中没有消息,那么 next 方法会一直阻塞。
Loop 的工作原理
1.基本介绍
- Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是它会不断的从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就会一直阻塞在那里。
- Handler 的工作需要 Looper,没有 Looper 的线程会报错
2.原理
- 构造方法:创建一个 MessageQueue,然后保存当前线程的对象。
- 通过 Looper.prepare() 为当前线程创建一个 Looper,然后就可以创建 Handler 的对象了,接着通过 Looper.loop() 方法来开启消息循环。
- 除 prepare 外,还有一个 prepareMainLooper 方法,主要是给主线程 ActivityThread 创建 Looper 使用的,本质也是通过 prepare 方法实现的。对应的,系统提供 getMainLooper 方法来在任意位置获取主线程的 Looper。
- Looper 终止:
- quit 方法:直接退出 Looper
- quitSafely 方法:设定退出标记,当消息队列中的已有消息处理完毕后安全地退出。
- Looper 退出后,通过 Handler 发送的消息会失败,此时 Handler 的 send 方法会返回 false。子线程中,如果手动创建了 Looper,那么所有事情做完后,应当调用 quit 方法来终止消息循环,否则这个子线程会一直处于等待的状态,而如果退出 Looper 以后,这个县城就会立刻终止,因此 建议不需要的时候终止 Looper。
- Looper.loop 方法
- 调用了 loop 方法后,消息循环系统才会 真正的起作用。
- 工作过程:
- loop 方法是一个死循环,当且仅当 MessageQueue.next 方法返回了 null 时,跳出循环。当 Looper 的 quit 方法被调用时,Looper 就会调用 MessageQueue 的 quit 或者 quitSafely 方法来通知消息队列退出,此时,next 的方法就会返回 null。
- loop 方法会调用 MessageQueue 的 next 方法来获取新消息,当没有信息时,next 会阻塞,这也导致 loop 方法一直阻塞。
- 当 next 方法返回新消息时,Looper 就会处理这条新消息:msg.target.dispatchMessage(msg),这里的 msg.target 是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage 方法来处理了。
- Handler 的 dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就将代码逻辑切换到制定的线程中了。
Handler 的工作原理
1.原理
Handler 的工作包含消息的发送和接收过程。
-
消息的发送:
- 通过 post 的一系列方法和 send 的一系列方法来实现,post 相关方法最终是通过 send 相关方法来实现的。
- Handler 发送消息只是向消息队列中插入一条消息,MessageQueue 的 next 方法就会返回这条信息给 Looper,Looper 收到消息后就会开始处理这条消息,最终消息由 Looper 交由 Looper 处理,即 Handler 的 dispatchMessage 方法会被调用,此时进入处理消息的阶段。
-
消息的处理
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
流程如下:
-
首先,检查 Message 的 callback 是否为 null,不为null 就通过 handlerCallback 来处理消息。Message 的 callback 是一个 Runnable 对象,实际上就是 Handler 的 post 方法所传递的 Runnable 参数。
private static void handleCallback(Message message) { message.callback.run(); }
-
其次,检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handleMessage 方法来处理消息。
public interface Callback { /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public boolean handleMessage(Message msg); }
通过 Callback 可以如下方式创建 Handler 对象:Handler handler = new Handler(callback)。这样做的意义在于提供另外一种无需派生 Handler 子类并重写其 handlerMessage 方法处理具体消息的 Handler 的使用方式。
-
最后,调用 Handler 的 handlerMessage 方法来处理消息。只是一个空方法,需要我们派生 Handler 子类时,重写该方法。
public void handleMessage(Message msg) { }
-
-
补充:
-
Handler 还有一个特殊的构造方法,通过一个特定的 Looper 来构造 Handler
public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
原文中说“通过这个构造方法可以实现一些特殊的功能”,然而因为我接触事件尚短,不清楚具体能做些什么。。。
-
默认构造方法中对 Looper 对象进行判断,如果为空的话,那么就会报这个常见的异常"Can't create handler inside thread that has not called Looper.prepare()"。
public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
-
主线程的消息循环
消息循环模型
- Android 的主线程 ActivityThread 的入口方法 main 中,会通过 Looper.prepareMainLooper 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop 方法来开启主线程的消息循环。
- 主线程的消息循环开始之后,ActivityThread 需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H,内部定义了一组消息模型,主要包含四大组件的启动和停止等过程。
- ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中执行。
- 以上即为主线程的消息循环模型。