handler搬运消息给MessageQueue都是在Java中做简单的调用
需要更加深入理解handler的原理,我们需要把目光再拉远,看得更多,细节也会更多。
首先,UI线程是用来更新界面的一个特殊存在,ActivityThread就是activity存在的线程,android只允许UI线程更新界面的目的就是为了保证线程的安全。
Looper的初始化
ActivityThread就是一个简单的java类,他有一个main函数作为主入口。
首先在main函数中初始化一个looper,并且记为mainLooper(因为他是main中的looper,也是更新ui的主要looper)
Looper.prepareMainLooper();//初始化一个Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
......
sMainLooper = myLooper();//记录mainLooper
}
}
重点关注prepare函数
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//已经存在looper,throw Exception
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
唉,好像看到一个熟悉的错误Only one Looper may be created per thread
在此处判断,如果存在了looper,就抛出异常
不过sThreadLocal是什么?ThreadLocal有什么用?(拓展知识点)
在上述代码中,sThreadLocal用到了get 和 set 方法,我们可以把sThreadLocal想象成map结构,key就是当前Thread,value就是想要set的对象。set时自动识别到当前线程。因此在map中保证了只有唯一的key(也就是Thread),那么从sThreadLocal中取出的looper也就保证了唯一,而且在使用的过程中,通过sThreadLocal就进行了线程的切换。
ThreadLocal确保了每个线程都有唯一一个looper,这也解释了通过looper可以达到切换线程的目的。
main函数的其他初始化操作都完成了后,就通知开启loop,准备从消息队列中获取消息,再把消息分发出去 dispatchMessage()
Looper.loop();//开启loop
MessageQueue的初始化
MessageQueue的初始化就在Looper的构造函数中
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
MessageQueue构造函数中做了两件事,暂时只关注nativeInit方法
native的Looper和MessageQueue
nativeInit是一个native端的方法(底层方法),他主要干了两件事,创建native端的MessageQueue(这个MessageQueue不是java层的,后面用nativeMessageQueue表示);返回nativeMessageQueue的地址;
mPtr就是nativeMessageQueue的地址
对应的,native端也有一个Looper(我们后面也把他叫做nativeLooper)。
nativeLooper也干了许多事,不过我们不用太关注细节,只要知道nativeLooper有一套机制(epoll):想象每个通知路径就是一条水管,数据在水管中传输,哪个数据发生了变化,epoll会观察到并进行通知。
emmm,涉及到native方法了。
需要交代一个底层的原理:epoll (拓展知识点)
我们平时发送网络请求,传输文件,pipe(ipc机制)这些都是数据的传输,我们把它叫做流(想象java的输入输出流)。流的一端为写入(write),另一端为读取(read)。
如果我需要读取数据(read),可是另外一端暂时没有写入(write),是不是就的干等着?
这就相当于是阻塞,我啥事都不能干,只能这么等待,这效率也太低了吧。
所以,epoll的作用就来了,它会帮你观察何时数据被写入(write),当数据来临时,通知你进行读取(read)。有epoll的存在,我就不用这么累了,可以去休息。
相对于线程来说,就不会存在线程阻塞问题,线程不阻塞,cpu就不会瞎忙活。平时总是被问到looper为什么不会被阻塞,原因就在此。
Handler的搬运消息
了解了epoll机制后,再来看看handler的搬运消息
//MessageQueue的enqueueMessage()
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; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
如果:
消息队列为空;
新消息处理时间==0(立即处理);
新消息处理时间小于队列头的处理时间;
我们就把新消息插入队列头,同时现场时休眠状态,就需要把它唤醒(nativewake),处理一下新消息。
其余情况:
我们就把新消息按照处理时间插入队列中
Looper.Loop()
底层溜达了一圈,最终还是得回到java层。
Looper.loop();
loop函数开启了消息的接收,只要存在消息,就对消息进行分发
再次回顾下如何从messageQueue中获取消息
//MessageQueue的next()方法
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//从队列中拉取消息,nextPollTimeoutMillis=0表示立即拉取
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) {
// now:当前时间 msg.when:消息的通知时间
// 消息的通知时间还不到,计算下次拉取message的时间
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;
}
}
nativePollOnce(ptr, nextPollTimeoutMillis);
首次调用就先调用nativeLooper去请求看有没有message,没有新消息就尝试进行休眠(不阻塞)。再也不用担心线程傻等了。
Handler.dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
分3种情况处理消息:
给消息添加callBack回调
handler自带的callBack
handleMessage方法
总结:
-
线程中调用prepare()函数新建Looper,ThreadLocal保证了线程的唯一性;
-
创建looper时,同时也创建了MessageQueue
-
MessageQueue的构造函数中又创建了nativeMessageQueue,mPtr则记录了nativeMessageQueue的地址。java层就变相的应用了底层epoll机制,再也不怕阻塞了。
-
Handler的创建跟随线程,当handler把消息插入MessageQueue时,按照处理时间先后顺序排序,如需唤醒线程,就调用nativeWake()
-
Looper.loop()则不断从MessageQueue中获取,如果处理时间没到,则继续休眠。
-
Looper获取到新消息后,message持有handler的应用,使用dispatchMessage分发消息,不同的callback,处理不同的消息。
网友评论