Handler
Android 有一条非常重要的开发规范限制:不能在子线程访问UI控件,否则程序异常。所以经常通过Handler来将更新UI的操作切换到主线程中执行。
为什么不能在子线程访问UI控件?
因为Android UI控件不是线程安全的,多线程并发访问,或处于不可预期的状态。如果对UI控件加锁,会降低UI控件访问的效率,会阻塞某些线程的执行。
消息队列
MessageQueue包含插入enqueueMessage和读取next两个操作,他的内部实现是通过一条单链表的数据结构来维护消息列表
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 注意此处也是一个 for 循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// native 层阻塞函数,nextPollTimeoutMillis 为超时时间,首次循环时值为0,即直接返回
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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一个消息还没到处理时间,则设置超时时间为还需等待的时间,进入阻塞状态.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// 下一个节点成为表头
mMessages = msg.next;
}
// msg 需要取走处理,故需要从链表中断开
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 标记为使用中
msg.markInUse();
// 返回要处理的消息
return msg;
}
} else {
// 没有消息要处理,超时时长为-1,循环并等待
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// mBlocked 标记为 true,进入阻塞状态,有新消息入队时,会调用 nativeWake() 唤醒
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next方法里有个for循环,是一个死循环。如果消息队列中没有消息,next会阻塞在这里;有消息时,会返回这条消息,并从链表中删除
Looper
handler的工作需要looper。通过Looper.prepare为当前线程创建Looper,然后通过Looper.loop()来开启消息循环。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// 循环读取消息并处理,无消息时阻塞。这种写法是最常用的 Linux IO 操作方式。
for (;;) {
// 取出一个消息,若没有消息要处理,则阻塞
Message msg = queue.next(); // 可能阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 调用 handler 的 dispatchMassage() 分发消息
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// 回收进对象池
msg.recycleUnchecked();
}
}
loop方法是个死循环,他会调用MessageQueue的next方法来获取新消息,而next是个阻塞方法,所以loop也会阻塞。
主线程的死循环一直运行是不是特别消耗CPU资源呢?
public static void main(String[] args) {
// 省略...
// 初始化 UI 线程的 looper 对象
Looper.prepareMainLooper();
// 初始化 ActivityThread,进而初始化其成员变量 mH(Handler子类)
ActivityThread thread = new ActivityThread();
// 将 ApplicationThread(Binder) 对象 attach 到 ActivityManagerService(AMS)
// 注:AMS 运行在 SystemServer 进程的一个线程中,负责调度四大组件等,通过 Binder 与 App 进程进行 IPC
thread.attach(false);
// 省略...
// 主线程进入循环
Looper.loop();
}
这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
ThreadLocal
线程内部的数据存储类。当某些数据是以线程为作用域且不同线程有不同数据副本时候,可以采用ThreadLocal。对于Handler来说可以获取当前线程的looper
private ThreadLocal<Boolean> mTLocal = new ThreadLocal<>();
mTLocal.set(true);
new Thread(){
public void run(){
mTLocal.set(false);
}
}
网友评论