深色模式
Android Handler 机制
Handler 机制简介
应用层 - 发送消息
应用层发送消息,可以使用以下方法:
Handler的
postXX()
系列:post
postAtTime
postAtTime
postDelayed
postDelayed
postDelayed
postAtFrontOfQueue
Handler的
sendXX()
系列:sendMessage
sendEmptyMessage
sendEmptyMessageDelayed
sendEmptyMessageAtTime
sendMessageDelayed
sendMessageAtTime
sendMessageAtFrontOfQueue
View的
postXX()
系列 (实际上也是通过Handler发送消息。):post
postDelayed
以上3个系列的方法,最终都会调用MessageQueue的enqueueMessage()
方法,enqueueMessage()
的作用就是将消息插入到消息队列中。
总结:
Handler.sendMessage()
-> MessageQueue.enqueueMessage()
。
应用层 - 处理消息
处理消息的方式有3种,可以从Handler的dispatchMessage()
方法看到:
java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
- 执行Message的
callback
,优先级最高。 - 执行Handler对象的
mCallback
,优先级次之。 - 调用Handler对象的
handleMessage()
方法,优先级最低。
Framework层 - 插入消息
通过MessageQueue的enqueueMessage()
方法,将消息插入到消息队列中。
Framework层 - 分发消息
在Looper的loop()
(新版本源码位于loopOnce()
)方法中,取到消息以后,会调用msg.target.dispatchMessage(msg);
,将消息分发到target
,也就是Handler,由Handler处理消息。
Handler 常见问题
为什么读取消息时,
for
循环不会导致ANR?Looper的
loop()
方法中有一个for
死循环,是为了在线程生命周期内不断读取消息;MessageQueue的
next()
方法有一个for()
死循环,是为了读取到消息之前不要退出next()
方法,其中,读取消息基于Linux的epoll机制,在收到消息前线程会进入等待状态,不会占用CPU。Handler如何实现跨线程通信?
首先,创建Handler的线程是接收线程,Handler无论在哪个线程中发送消息,都是调用
Handler.enqueueMessage()
方法,将消息插入到与Handler关联的MessageQueue中,而MessageQueue - Looper - Thread是一一关联的关系,而MessageQueue所关联的线程就是接收线程,所以实现了从其它线程发送消息到接收线程的线程通信。子线程创建 Handler 需要注意什么?
初始化 Looper,即调用
Looper.prepare()
;开启消息循环,调用 Looper 的
loop()
方法。
Handler原理
构造方法
java
public Handler(@Nullable 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对象,所以在使用Handler之前,要保证当前线程的Looper已准备好。
发送消息:enqueueMessage()
Handler中各种发送消息的方法,最终都会调用Handler的enqueueMessage()
方法:
而Handler的enqueueMessage()
方法中调用了MessageQueue的enqueueMessage()
方法:
java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
处理消息:dispatchMessage()
Looper获取到消息以后,会通过dispatchMessage()
方法,将消息分发,交由应用层处理:
java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handleMessage()
handlerMessage()
是一个空实现,应用层一般在这里实现业务逻辑:
java
public void handleMessage(@NonNull Message msg) {
}
ThreadLocal原理
ThreadLocal的作用
ThreadLocal用于操作线程私有变量,线程私有变量只有本线程才可以访问到。
线程私有变量存储在ThreadLocalMap结构中。
读写线程私有变量
ThreadLocal中的源码:
java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get()
获取当前线程的
threadLocals
,它是一个ThreadLocalMap,命名为map
;从
map
中读取变量,key就是当前ThreadLocal类的实例,也就是get()方法所属的对象;如果
map
为null
,则初始化threadLocals
(setInitialValue()
),并返回null
。
set()
获取当前线程的
threadLocals
,它是一个ThreadLocalMap,命名为map
;将变量存入map中,key就是当前ThreadLocal类的实例,也就是get()方法所属的对象;
如果
map
为null
,则先初始化threadLocals
,再存入。
ThreadLocalMap
部分源码:
java
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
本质上是一个hash表结构,初始容量是16,扩容阈值是2/3,每次扩容乘以2,与HashMap很相似。
有个不同地方在于,处理hash冲突时:
ThreadLocalMap采用线性探测法(开放地址法);
HashMap采用链地址法。
Looper原理
Looper的作用是对消息进行轮询。
Thread - Looper - MessageQueue一条龙绑定。
prepare()
prepare()
方法用于在当前线程内初始化 Looper 对象,建立起消息循环。
java
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()
的主要作用是创建Looper对象,并将Looper对象与当前线程关联起来(将Looper对象保存在线程本地变量里面)。quitAllowed
参数:允许退出。主线程的Looper不允许退出,其它线程的Looper允许退出。
Looper()
构造方法:
java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 创建MessageQueue对象。
- 获得当前线程的引用。
loop()
初始化 Looper 以后,需要调用loop()
方法,使 Looper 在本线程开始消息轮询。
java
public static void loop() {
//...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
}
这里的for
循环,是为了让读取消息的动作一直进行下去,也就是不停地执行loopOnce()
,除非Looper退出,这个for
循环才会结束。
注意,主线程的Looper不允许退出,它在ActivityThread的main()
方法中初始化:
java
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
//...
}
MessageQueue原理
插入消息:enqueueMessage()
插入消息时,通过Message的when
字段来确定插入到链表中的位置,when
越小,位置越靠前,when
等于0时,直接插入到表头。位置靠前的消息会被优先读取。
java
boolean enqueueMessage(Message msg, long when) {
//检查消息是否有target,target是Handler对象
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
//检查Message是否正被使用
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//检查消息队列是否正在退出
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();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//将消息插入到队列头
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//将消息插入到队列中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
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;
}
读取消息:next()
java
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//...
}
}
}
这里的for
循环,是为了在读取到消息之前,让next()方法不要退出。
nativePollOnce()
方法是一个本地方法,底层基于Linux的epoll机制,线程会等待而不占用CPU。所以这里的for
循环不会导致卡死。
同步屏障
同步屏障机制
在读取消息时,会从表头依次读取,如果读取到同步屏障,则暂不处理屏障之后的同步消息,而优先处理屏障之后的异步消息,异步消息全被处理完,同步消息仍然不会被处理,除非屏障被撤消。
当队列有同步屏障时,会优先处理异步消息。
三种消息:同步消息、异步消息、同步屏障
同步消息
不做特殊处理,通过Handler发送的普通消息都是同步消息。
异步消息
判断异步消息:Message的
isAsynchronous()
方法返回true
,则此消息是异步消息。调用Message的
setAsynchronous()
方法,可以将消息设置为异步消息。(API 22可用)如果创建Handler时,在构造方法中传入的
async
参数为true
,那么此Handler发送的消息默认都是异步消息。(应用层不可用)同步屏障
Message的
target
字段是null
,则此消息是同步屏障。MessageQueue的
postSyncBarrier()
和removeSyncBarrier()
方法,可以插入、移除同步屏障。(应用层不可用)Framework层,在ViewRootImpl的
scheduleTraversals()
方法中有用到同步屏障,是为了更快的响应UI刷新。
Message原理
Message对象池的实现
Message类中的源码:
java
Message next;
public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
其中:
sPool
:表头Message。next
:下一个Message。sPoolSize
:对象池当前大小,即链表长度。MAX_POOL_SIZE
:链表的最大长度。sPoolSync
:是用于同步操作的对象锁。
回收消息时,从表头插入;
重新复用消息时,从表头读取。
复用Message的方法
Handler的
obtainMessage()
方法;javaMessage msg = mHandler.obtainMessage(); msg.what = 100; msg.obj = obj; mHandler.sendMessage(msg);
Message的
obtain()
方法:javaMessage msg = Message.obtain(mHandler); msg.what = 100; msg.obj = obj; mHandler.sendMessage(msg);