1、简介
你在网上看了很多handler的原理,是不是有迷茫的地方,为什么是那个样子的?
总是听大家侃侃而谈,谈的内容还是千篇一律,流程原理说的也是那么个回事,但是再往深问一点,又啥啥不会。。。
如果你觉得有必要深深的理解它,记住它,请给我一起看看它是如何实现的
handler是android提供的一个可以在线程间传递消息的机制,主要涉及类有:Looper,MessageQueue,Message; 我们先从消息对象来看源码吧
2、消息Message
不仅是一个数据类,还实现了Parcelable接口,而且也包含了一个单链表的数据结构,还通过对象池,实现了复用
2.1 数据类
public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
public int sendingUid = UID_NONE;
public int workSourceUid = UID_NONE;
int flags;
public long when;
Bundle data;
Handler target;
Runnable callback;
Message next;
主要包含一下数据内容
- 消息内容:
- 消息标志what
- 整数型数据arg1,arg2
- 其它类型数据obj
- 进程间使用携带相关数据,data、sendingUid、workSourceUid,Messenger
- 消息发送对象target
- 消息执行时间戳when
- 实现单链表结构下个节点 next
- 消息到期时执行任务 callback
- flags标志:0有效状态,低位1使用过可重复利用状态,次低位异步状态
对于跨进程使用,则主要涉及轻量级跨进程通信Messenger机制,有兴趣的可以自行了解,这里暂时不介绍了
2.2 池技术使用
池是啥?简单理解:池是使用过对象进行缓存,需要使用对象时,从缓存拿实例,进行变量重新设置即可;好处就是减少对象的创建,减少内存剧烈波动
这里是池技术相关变量
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;
private static boolean gCheckRecycle = true;
gCheckRecycle变量这里不可直接操作可以通过反射来设置;其值改变方法被hide了而且这个方法只有在L版本一下,才会改变;另外池的复用主要是这个变量,为true表示已经使用过的对象
/** @hide */
public static void updateCheckRecycle(int targetSdkVersion) {
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
gCheckRecycle = false;
}
}
池数据使用了单链表结构,先入后出的操作逻辑吗,sPool链表头;池中最大数据为50个,每次回收对象时,则重置对象的所有相关数据
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
从链表的表头拿出数据
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0;
sPoolSize--;
return m;
}
}
return new Message();
}
3、消息队列MessageQueue
主要使用了队列进行消息的排队,也是单链表结构,按照时间递增排序;还是用了原生方法进行等待,唤醒操作
3.1 队列逻辑
是一个符合消费者模式的队列 相关变量
Message mMessages; // 消息队列对头
private boolean mQuitting; // 是否结束
private boolean mBlocked; // 是否被阻塞
入队操作
入队前,先检查状态
- 消息处理对象handler是否不为空
- 消息是否是有有效状态
- 是否已经退出,退出,则直接回收message对象
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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();
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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
入队过程
- 对消息处理,标记已经使用,并对触发时间赋值
- 加入队列头部
- 队列为空
- 新加入的消息时间戳为0
- 新加入的时间戳小于队列头部消息的时间戳
- 如果2不步骤中条件不满足,则从队列中找到最后一个时间戳小于when的节点,并插入其后面
- 如果队列为空,加入成功后,则进行唤醒
出队操作
出队操作是一个阻塞操作
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
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 {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
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;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
大致流程如下
- 判断原生地址,有效,则进行原生阻塞方法调用
- 如果是同步,则查找第一个有效的消息对象;这里有效的消息对象满足下面两点
- 消息本身不为空
- 消息的处理handler不为空
- 如果消息不为空且当前时间戳大于消息时间戳,则返回当前消息
- 如果不满足3,首先如果已经退出,则销毁原生对象,返回空消息
- 如果消息对象为空,则原生等待,直到等待被唤醒
- 如果消息不为空,唤醒时长为int最大正整数和消息时间戳减去当前时间戳的最小值
3.2 原生方法
原生对象映射java对象中,其实是把地址java成员变量中,java方法通过把指针来操作原生对象
这里mPtr为原生对象地址
nativeWake方法
nativePollOnce方法
4、发起接收者Handler
消息的发送,是有Handler发起的,消息的接收也是由handler来处理;其过程通过代理来实现的
主要相关变量
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
4.1 内部代理
- 消息发送通过mQueue代理,实现消息查找、发送、删除操作;其时间戳计算根据操作不同而不同
- 延时发送,时间戳为:当前时间戳+延时时间
- 立刻发送,时间戳为:当前时间戳
- 某个时间发送:为自定义时间
- 静态生成Message对象,由Message代理
4.2 重要方法方法
dispatchMessage方法在消息被处理时在Looper类中调用
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其处理方法的优先级: 消息对象的回调(callback) > 回调(mCallback成员变量) > handleMessage方法(空方法)
Handler静态创建方法
public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
if (looper == null) throw new NullPointerException("looper must not be null");
if (callback == null) throw new NullPointerException("callback must not be null");
return new Handler(looper, callback, true);
}
public static Handler getMain() {
if (MAIN_THREAD_HANDLER == null) {
MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
}
return MAIN_THREAD_HANDLER;
}
public static Handler mainIfNull(@Nullable Handler handler) {
return handler == null ? getMain() : handler;
}
给定looper的handler,主线程Handler;返回给定handler的非空版本
5、消息传送带 Looper
主要是取出队列中消息,并分发给handler处理消息
5.1 主要变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;
private static Observer sObserver;
final MessageQueue mQueue;
final Thread mThread;
sThreadLocal静态实例,保存了线程的looper对象
sMainLooper:主线程looper对象
sObserver:监听消息执行状态:开始,结束,异常
mQueue:消息排队队列
mThread:looper对象相关的线程对象
5.2 构造器
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
构造器是私有方法,提供了静态方法,来获取主线成looper对象,当前线程looper对象
5.3 启动消息分发处理
启动要调用静态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;
..........(省略一些代码)
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......(省略一些代码)
if (observer != null) {
token = observer.messageDispatchStarting();
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......(省略一些代码)
msg.recycleUnchecked();
}
}
流程大致如下:
- 首先取出当前线程的looper,如果为空则抛出异常;所以需要需要设置当前线程looper对象到sThreadLocal中,这就需要prepare系列方法
- 开始handler处理前监听
- 调用handler的dispatchMessage方法,如果出现异常,则调用监听器异常处理,无异常调用监听器正常结束状态处理
- 消息对象进行池回收
6、原理总结
这里主要分析了线程间通信的机制;进程间通信机制依赖Messenger通信机制;下面对线程间通信进行一些总结
- handler对象是消息发送者,和消息的处理者;
- 消息能够运行,需要先调用Looper的prepare方法,再调用loop方法,启动循环
- Message携带消息信息,而且使用了池技术;对象构造时,应从池中获取
- 消息的处理方法并不是固定,具有优先级关系:Message对象中callback > handler对象中mCallback > handler中handleMessage方法
- 消息队列退出后,looper循环也会跟着一起退出
- 消息队列采用消费者模式,并根据时间戳进行等待;其等待唤醒采取native方法(暂时在androidRef和自己下载的framework源码中未跟踪到实现,暂时未能进行分析给与歉意)
- 队列阻塞为何用原生方法,而不直接用线程挂起唤醒呢?很简单,主线程是UI线程,如果是主线程的消息,还要不要刷新界面了,这里的阻塞机制,类似与协程的实现;主线程的消息机制可以直接用,因为在ActivityThread类中已经调用Looper的预加载,启动方法了
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!