Handler是Android中最常用线程通讯方式之一、也是非UI线程与线程通讯的主要方式。
你可能有个疑问基础api中AsyncTask、runOnUiThread()还是第三方的RxJava、Eventbus内部都是直接或间接使用Handler实现对UI线程进行更新(参照源码)。
//-- runOnUiThread
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
//-- Eventbus
package org.greenrobot.eventbus;
import android.os.Handler;
public class HandlerPoster extends Handler
// Rxjava
Flowable.create(..., BUFFER).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
那么既然Handler这么牛逼那么它内部是怎么实现更新UI的,以及为什么它能够更新UI线程呢?我相信做Android的或多或少的看过相关的资料,了解Handler的核心机制由Looper、Handler、MessageQueue、Message四大金刚完成;
1.Looper发动机负责转动整个消息队列
2.Handler执行者取队列任务干活的工具人
3.MessageQueue任务链表队列、一个挨着一个的那种
4.Message任务详细信息,以及任务所需要的数据(序列化Parcelable)
那么我们现在一个一个来解读(没有比代码更让我们程序员感到亲切的了,以下...为忽略非关键代码)源码阅读
1.Looper
// sThreadLocal.get() will return null unless you've called prepare().
// 每个线程里有且只可能有一个Looper、而所有的线程对应的Looper都存在这里面
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// UI线程的主Looper,在ActivityThread中的main函数启动的时候创建
private static Looper sMainLooper; // guarded by Looper.class
private static Observer sObserver;
// 这么快就出现了伙计,当前Looper的消息队列。
final MessageQueue mQueue;
// 当前Looper所在的线程
final Thread mThread;
...
private Looper(boolean quitAllowed) {
// 参数为是否可安全退出(是否执行完任务再退出)
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
...
private static void prepare(boolean quitAllowed) {
// 一个线程里面只能存一个Looper,所以多次prepare会Exception
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// 初始化主线程的Looper,不需要我们调用
public static void prepareMainLooper() {
...
}
// 永动机启动
public static void loop() {
final Looper me = myLooper();
...
Binder.clearCallingIdentity();//(native方法切换进程身份pid/uid这里不详细讲解了)
final long ident = Binder.clearCallingIdentity(); // 你没看错第一次调用是检查、第二次调用才是切换进程id跟用户id,不然没办法访问ui的
...
final MessageQueue queue = me.mQueue;// 取队列
...
// 上来就是一个死循环,屌不屌
for (;;) {
// 取消息队列取到就往下执行,取不到轮循(并非空转、后面会讲next方法的机制)
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
// Handler通过dispatchMessage分发Message
msg.target.dispatchMessage(msg);
...
// 切换原来的身份
final long newIdent = Binder.clearCallingIdentity();
...
// 用完了的Message回收
msg.recycleUnchecked();
}
}
上面是我提炼出来的核心代码(我认为)
1).prepare:在当前线程初始化Looper(同一线程禁止重复调用\线程与Looper为1:1关系)
2).loop:检查并切换pid/uid,通过死循(并非一直空转)环轮询MessageQueue,通过Message中的Handler分发消息,切换回自己的pid/uid,Message对象回收进对象池
3).quiteSafely: 安全退出/非安全退出
2. Handler
// 平时如果想用主线程的Looper更新UI用它用它用它getMain(),不用再new了新版本sdk才支持getMain
private static Handler MAIN_THREAD_HANDLER = null;
public Handler(@Nullable Callback callback, boolean async) {
...
// 获取当前所在线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(...);
}
// 取Looper中的消息队列
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
// 执行消息回调
public void dispatchMessage(@NonNull Message msg) {
//优先执行message中的Callback,想不到吧我也有callback
if (msg.callback != null) {
handleCallback(msg);
} else {
// 否则执行Handler中的callback
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后才轮到handMessage我最常用的方式确是优先级最低的,衰。
handleMessage(msg);
}
}
...
// Message对象池取对象,没有则创建
public static Message obtain(...){...}
...
// 直接将发送自己创建Message实体
public boolean sendxxx(){}
// 发送回调接口让Handler帮我们创建Message
public boolean postxxx(){}
...
// 不管你是那条道,终点都在这
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 发送消息前将自己作为执行者赋值给Message以便于后面轮到自己的时候有人执行。
msg.target = this;
...
// 加入到消息队列,队列会根据时间排序入列
return queue.enqueueMessage(msg, uptimeMillis);
}
// 没它啥都干不了
final Looper mLooper;
// 执行者负责根据需求创建任务放入列表
final MessageQueue mQueue;
// 远程服务binder中的service,用于进程通讯 对就是用来进程通讯的
IMessenger mMessenger;
// 远程服务的实现
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid();
Handler.this.sendMessage(msg);
}
}
Handler的主要职能就是对外暴露通讯接口(通讯起点\终点),Message对象构造回收。
- send/post 通讯起点
- obain 对象复用
- enqueueMessage入列消息
- dispatchMessage 通讯终点
3. Message
// 通讯的数据包
Bundle data;
// 执行者
Handler target;
// 下一位
Message next;
// 对象池(链表)中的头部
private static Message sPool;
// 对象池中对象数量
private static int sPoolSize = 0;
// 对象池中的存储上限
private static final int MAX_POOL_SIZE = 50;
...//这里还有各种状态机、基础数据、进程uid等等
// 使用缓存的空Message,没有则创建
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
// 取对象池(链表)中的对象,同时链表链表前移
...
}
}
return new Message();
}
// 其实就是深拷贝
...obtain(...){...}
// 使用完清理回收
void recycleUnchecked() {
// 清空状态、数据、回调
flags = FLAG_IN_USE;
...
// 加入到缓存队列中,修改计数器,溢出则不入列
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
...
}
}
}
- obtain:对象池出列
- recycleUnchecked:对象池出列
- flags:状态机
4. MessageQueue
// 消息
Message mMessages;
// 未执行的 补充操作队列(UI刷新的dispatch执行完了 可以补充做一些处理)
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 文件描述符
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
// 正在执行的 补充操作队列
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// 是否阻塞
private boolean mBlocked;
...
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
// 创建消息队列-底层队列c/c++
mPtr = nativeInit();
}
...
// 核心方法取队列
Message next() {
// 底层native的消息队列头指针
final long ptr = mPtr;
if (ptr == 0) {
return null;// java队列与native队列必须同步,否则返回空结束loop轮询
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 下次执行的时间
int nextPollTimeoutMillis = 0;
// 又一个死循环不停的在queue中取消息
for (;;) {
if (nextPollTimeoutMillis != 0) {
// 调用native方法查看当前是否需要挂起当前队列(CPU繁忙的情况下)
Binder.flushPendingCommands();
}
// 同步native消息队列出列下一个要执行的消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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) {
// Next message is not ready. Set a timeout to wake up when it is ready.
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;
}
// 进程或者线程结束 销毁队列包括native队列结束轮循
if (mQuitting) {
dispose();
return null;
}
...// 取当前未执行的 补充操作队列
}
...// 执行补充操作队列
}
}
// 添加消息到队列
boolean enqueueMessage(Message msg, long when) {
...// 消息状态判断
synchronized (this) {
if (mQuitting) {
...// 队列已经销毁退出
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
// 是否需要唤醒
boolean needWake;
// 空队列或者执行时间小于头部Message的执行时间
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;
// 根据Message when执行时间顺序插入到链表中
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;
}
// 唤醒队列,传入native队列的头指针
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
...//还有一些其它的代码跟队列无关,native文件描述符监听,看命名跟dns tcp网络相关这里就不过多解读了
MessageQueue作为一个队列内部的大量逻辑跟native相关,因为涉及到cpu资源利用等一系列性能问题所以使用Java队列+Native队列,比如防止死循环的空转导致大量CPU资源消耗、CPU资源紧张的时消息未到执行时间则挂起等等,ideHandler(跟Handler无关)在App启动时候因为cpu资源紧张但又想不让队列挂起所做的补充操作,文件描述符监听等等代码。
1). next: 消息出列
2). enqueueMessage: 消息入列
最后我们常常使用Handler导致内存泄漏,是因为Message会持有Handler/Callback的引用,而这两者经常是内部或者匿名类会持有外部类的引用(如Acitivty),在Message为delay操作时Acitivty退出销毁因为被Message引用而导致内存泄漏;而自线程有能够访问ui线程主要还是通过native中切换线程uid来实现的(Linux系统下线程资源访问通过uid识别),而这个由native实现 感兴趣的小伙伴可以看看Binder. clearCallingIdentity的源码。
今天就记录这么多了,第一次写技术长文有些瑕疵希望有心看到这篇记录的不要介意。
下次我来补充讲一下ThreadLocal相关的内容,因为这个东东很多小伙伴也有许多疑问。
网友评论