Android 消息处理机制
Android 消息处理机制
Android中常说的消息处理机制, 其实就是android下线程间的通信机制, 也就是Handler的工作机制.
Handler, 我想对于每一个有android开发经验的人来说都不会陌生, 经常使用到.
比如, 我们要在子线程中更新界面UI, 会通过handler.sendMessage()发送一个消息到主线程; 然后在handler的回调方法handleMessage()中去处理这个消息.
我们都知道, android中更新UI只能在主线程进行, 那么, 不知道你有没有想过, 为什么在子线程中可以通过handler发送消息给主线程, 来通知主线程更新UI呢?他们是怎么通信的呢?是不是只有主线程才能用Handler的消息机制呢?
一, android消息机制主要由Handler, Message, MessageQueue, Looper几大角色组成:
以前网络收藏的图Handler: 负责发送和处理消息
Message: 消息的载体, 封装了what, arg, target等相关的信息.
MessageQueue: 消息队列, 存放由Handler发送过来待处理的Message.
Lopper: 初始化Looper的同时会创建一个MessageQueue, Looper启动后会一直不断的从MessageQueue中取出消息Message交给对应的Handler来处理
二, 我们以mainThread为例, 来分析下Handler机制的工作流程
1. Looper初始化
系统启动时, 在ActivityThread.java的main()中就已经初始化好了, 然后通过Looper.loop()来开启looper
public static void main(String[] args) {
//...
//创建Looper
Looper.prepareMainLooper();
// ...
//启动Looper
Looper.loop();
}
接下来先看下Looper.java中创建looper的代码
/** 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) {
//如果looper已存在, 抛异常,,保证looper的唯一
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
由以上代码可以看出, 在prepare(boolean quitAllowed) 中我们new了一个Looper对象, 然后保存在sThreadLocal中, 然后通过myLooper()来创建sMainLooper, 即我们的主线程Looper.
代码逻辑很简单, 主要是这个sThreadLocal是什么东东?
sThreadLocal 是 ThreadLocal的一个实例. ThreadLocal又是神马, 不是很懂, 只知道是用来提供thread-local的, 提供了get, set方法. set会保存looper和当前线程的一些信息, get返回looper, 看这个类的注释了解下
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
* <pre>
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID, assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
...
public void set(T value) {
Thread t = Thread.currentThread();
//ThreadLocalMap is a customized hash map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
//ThreadLocalMap is a customized hash map
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();
}
...
}
顺便, 我们看下new Looper()做了什么
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
灰常简单, 就2行代码, 一个是初始化我们的MessageQueue, 然后就是获取到当前的线程mThread .
注意下这里的private构造方法, 即looper是单实例的, 也就是说, 只能通过Looper.prepare()来创建looper对象, 回过去看下prepare()的代码, 在创建looper前会先判断下looper是否存在, 如果是已经存在了会直接抛异常的, 这样就保证了一个线程只能有一个looper, 同时, 也就只能有一个MessageQueue;
2. 启动looper
嗯嗯,...sMainLooper创建好后, 回到ActivityThread.main(), 通过Looper.loop()启动looper.
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//拿到当前线程的looper
final Looper me = myLooper();
//looper为空就抛异常, 这就是为什么普通线程不能用Handler的原因了
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//拿到looper的MessageQueue
final MessageQueue queue = me.mQueue;
//...other code
//无限for循环
for (;;) {
//拿到MessageQueue中的Message对象, 如果有, 主要这里的might block, 即这个方法会阻塞线程
Message msg = queue.next(); // might block
//...
try {
//dispatchMessage分发处理Message
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//...other code
//Message回收
msg.recycleUnchecked();
}
}
看loop()的核心代码, 逻辑也很简单, 就是拿到当前线程的Looper, 拿到Looper的MessageQueue, 然后一个无限for循环, 不断的从消息队列里取出消息, 然后msg.target.dispatchMessage(msg), 来处理消息.
我们重点分析下MessageQueue是怎么拿到消息的:
Message msg = queue.next(); // might block
就是MessageQueue.next()了
Message next() {
// ...other code
int nextPollTimeoutMillis = 0;//等待执行的时间
for (;;) {
//消息需要延时执行, e.g: postDelayed, sendMessageDelayed这些操作就会有延时
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//这里, 如果没有消息的话/时间还没到, 会堵塞线程, 直到有消息/时间到了的时候, 才会被唤醒返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//找到一个不是异步而且不为空的message.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//时间还没到, 计算出延时时间, 这里之后会进入下一个循环, 然后在nativePollOnce()中block住
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 以下..顺利取到一个消息, 然后返回这个消息
mBlocked = false;
//链表操作
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages. 没有消息了
nextPollTimeoutMillis = -1;
}
// ...
}
// ...
}
}
looper的启动流程大概就到这里了, 这里有几个问题有注意下:
1. 调用myLooper()后, 会先判断下looper是否为空, 如果空会直接抛异常, 这就是为什么普通线程不能用Handler的原因了, 所有线程如果要有消息处理能力, 必须先Looper.prepare()来创建looper对象;
2. 分发消息msg.target.dispatchMessage(msg), 这里的target其实就是关联的handler对象, 是在sendMessage()的时候关联的, 这样能够使Looper在分发消息的时候是不用去区分Handler对象的;
3. queue.next()这里是会阻塞线程的, 而且这里如果是主线程的话, 为什么不会ANR呢? 这个问题要先理清楚产生anr的原因, 并不是有阻塞就会anr, anr是因为主线程的消息来不及处理而导致的. 比如, 我们在启动一个activity的时候再主线程sleep个10s, 这样不一定会anr的, 只有当主线程阻塞的时候, 有新的消息要处理又得不到处理, 比如我们按下手机返回键, 这时就会anr了.
3. obtain Message
获取一个消息对象, 一般使用obtain, 当然new也可以.
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
private static Message sPool;
这里的sPool是一个静态Message对象, 不为空的时候, 就直接返回这个message, 否则new一个message, 这样可以减少对象的创建. 那么这个sPool是那来的呢? 就是在Looper分发消息后, 可以回过去看一下loop()方法for循环的最后一行代码, 就是对message的回收操作.
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//将当前message缓存起来, MAX_POOL_SIZE = 50
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
回收前, 先把当前对象的以下状态, 参数等清除, 然后把当前对象保存在sPool. 刚才说了, 这个sPool是个静态变量, 所有是会一直存在的. 这里Message其实也维护了一个链表这种数据结构的, 看下在对sPool赋值前会把之前的sPool赋值给next, 就是让next指向之前回收的Message对象. 当obtain获取一个Message时, 获取到的是表头的Message, 然后又让sPool指向表头的next, 即下一个Message对象, 这样每次obtain时都能获取到表头的一个Message对象.
惊不惊喜, 意不意外? 以前没看源码前, 一直以为这边是用对象池的方式来缓存Message对象的.
4. Handler发送和消息的分配
Handler要发送消息, 要先获取一个Handler对象, 一般通过以下两种方式
- handler = new Handler()
- handler = Handler(Looper looper)
需要一个Looper对象才能创建Handler, 第一种方法其实默认获取当前线程的Looper的
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Looper.myLooper()获取的就是当前线程的Looper. 看下那个异常, 当looper为空的时候抛异常的, 这就是为什么不能再子线程用handler的原因了.
Looper在启动后, 会一直在死循环中, 等待消息进入消息队列.
那么, 消息是怎么进入消息队列的呢? 就是我们经常用的handler的一些方法了, 比如
sendMessage, sendMessageDelayed, post, postDelayed...
这些方法最终都会执行到sendMessageAtTime()这里
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //关联handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
然后就到MessageQueue.enqueueMessage()加入消息队列了
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
//消息执行的时间,队列是按照消息执行时间排序的
//when = SystemClock.uptimeMillis() + delayMillis
msg.when = when;
Message p = mMessages;
boolean needWake;
//列表里没有消息 / 消息的执行时间== 0 / 要入队列的消息执行时间 < 表头消息的时间
//把这个消息当做表头, next指向之前的那个消息(mMessages)
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
//需要唤醒线程, 因为在不用处理消息的时候线程是block的, 前面分析过了
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//从表头开始遍历消息, 根据执行时间找出前面(要先执行)的那个消息prev
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//插入链表的操作
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 唤醒线程
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
代码注释都有, 不难理解.
接下来分配消息, 看下Handler.dispatchMessage(msg)这部分代码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这部分代码逻辑很简单, 大概解释下: handler.post()时msg.callback != null, 会调用Runnable.run();
如果是通过Handler(Callback callback)实例化handler的话, mCallback != null; 否则直接调用 handleMessage(msg), 这个是空方法, 就是我们初始化Handler时重写的那个handleMessage()方法.
三, 利用Handler机制来实现子线程间的通信
根据主线程的消息处理机制的原理, 我们完全可以在子线程中来实现我们自己的消息处理机制.
大概代码是这样的:
class MyThread extends Thread {
@Override
public void run() {
super.run();
//prepare looper
Looper.prepare();
//handler
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
...
//start looper
Looper.loop();
}
}
这样, 只要在其他子线程能拿到MyThread的Handler或是Looper(创建Handler)就能向MyThread发送消息了, MyThread的looper会自行处理这些消息.
ps: 这几天在重新理了下Handler机制这块, 写下来是为了加深印象. 第一次写, 如有不对的地方, 请帮忙指正,感谢!感谢!!