Handler消息分发机制
1. 问题:
子线程如何更新UI
顺便解决在子线程中为啥handler要放在Looper中???
2. 资源
https://blog.csdn.net/bboyfeiyu/article/details/38555547
https://blog.csdn.net/guolin_blog/article/details/9991569
https://www.jb51.net/article/93199.htm(高深)
3. 分析
为啥我们要用Handler,因为Android UI线程不安全。为啥不安全,因为Android中UI线程操作并没有加锁,也就是说可能在非UI线程中刷新界面的时候,UI线程(或者其他非UI线程)也在刷新界面.这样就导致多个界面刷新的操作不能同步,导致线程不安全。如果,我们直接更新UI,但你不能做过多的耗时操作,否则你会使UI卡顿。
4. 流程
我们从UI线程(不用加Looper)开始分析消息分发机制
在Activity中 new Handler,然后我们去看看Handler的构造函数
最终它会有两个构造函数,我们只分析下面这个,另一个很简单就不提了,请看下图
第一个if只是用发射获取一些当前类的信息,并打印,不重要。
看mLooper=Looper.myLooper();
这是啥???
我们可以知道myLooper()返回给我们了一个Looper对象。
补充知识:
Handler是管理某个线程(也可能是进程)的循环消息队列,那它是不是要持有Looper和MessageQueue的对象。而我们看到handler中并没有设置Looper和MessageQueue成员变量。
那他们是怎么联系的???
通过Thread持有Looper、Looper持有MessageQueue,然后handler调用Thread.currentThread()获得当前线程Thread。
所以我们可以说Thread为handler绑定了Looper与MessageQueue。
Ok,现在我们应该理解到hander运行在Thread中,而Thread 可以帮我们获取到Looper与MessageQueue,至于Looper与MessageQueue是多久创建的我们后面说。我们已经创建好了一个handler,然后就是我们用的时候,调用handler方法sendMessage()
最后跟踪到
到了这里,就是把你发送的消息与当前的handler对象封装在Message中,和当前时间加上延迟更新时间一起传入enqueueMessage(),于是我们就通过这个方法把多线程消息有顺序的存储好了,比较简单不进去细看了。要读这个方法请注意Message是一个单链表,我们所有的数据都存在这个缓存单链表中,还要知道MessageQueue中的mMessages成员变量记录的是单链表的头。而且在这个方法中其用到了synchronized锁,保证了多线程时消息存放时的安全性。
好了,接下来我们就是依次的去取数据。如果我们在主线程(UI线程)中,自己写的代码层只要把数据发给handler,此时你的UI就更新了。但是,我们前面只解释了将多线程有序的缓存起来,怎么UI就更新好了。。。我们接下来说,UI handler在背后默默为我们做的事。。。
我们再补充点知识:
补充1:
UI线程(主线程)在程序启动时就开始创建了
在Android启动接口中的ActivityThread.main()方法中(不知道请百度)
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
我们可以看到其实在UI 线程中,系统调用Looper.prepareMainLooper()
我们可以看到调了prepare();
sThreadLocal是啥???在Thread类中有一个成员变量
ThreadLocal.ThreadLocalMap threadLocals
其内部维护了一个数组 Entry[] table ,而其内部类Entry 是一个Key-Value,类似于Map, 如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
所以每个线程都有一个自己的 "Map" 来存储一些东西,其中的Key 的类型为ThreadLocal ,value 为任意类型。 而在Looper中定义了一个唯一的Key:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
所以在上面展示的Loop.prepare()代码中的sThreadLocal.get的功能为向当前线程的Map中,取出Key为sThreadLocal对应的Value(Looper). 如下图:
t.threadLocals,就是上面提到的"Map"
所以sThreadLocal.get()查看UI线程是否有Looper对象,由于此时我们在安卓应用程序刚启动的入口,所以没有数据,于是,就创建了一个new Looper(),其构造函数如下:
好了 这样就解释了前面的留下来的问题,在Looper的构造函数中,我们为UIhandler 创建一个MessageQueue 并记录了当前线程Thread,
回到 ActivityThread.main()截图,接着系统为activity创建主线程线程ActivityThread,获得activity的与UI交互的handler等,注意最后一句Looper.Loop()是不是很熟悉。。这是用来开启无限循环取前面handler存储在Message中的数据的方法。
next()函数----- 从链表中取出消息
Message next() {
for (;;) {
// 如果下一个Message执行时间还没到,就会被该函数阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 如果msg.target == null,表示为同步屏障,用于同步异步消息。
// 当向链表插入(发送)一个同步屏障后,紧接着会向链表插入(发送)一个异步操作,一个Message.isAsynchronous=true 的消息到MessageQueue中。
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;
msg.markInUse();
return msg;
}
} else {
// 链表中没有消息
nextPollTimeoutMillis = -1;
}
// 是否放弃链表中的所用消息
if (mQuitting) {
dispose();
return null;
}
// 判断当前线程是否空闲,如果空闲,执行IdleHandler中的不紧急事件。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果没有需要执行的事件,阻塞线程
if (pendingIdleHandlerCount <= 0) {
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; // release the reference to the handler
boolean keep = false;
try {
// 钩子函数,执行非紧急事情,返回true,表示成功。
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;
}
}
取消息时,可能会做以下事情:
1. 如果获取的消息是同步屏障,则向后遍历,找出链表中离同步屏障最近的一个异步操作,并执行。(注意:同步屏障没有被删除,一般在异步操作执行的内部调用函数将屏障删除。)
2. 如果链表中没有消息或者消息的执行时间还没到,表示空闲。就可以执行MessageQueue.mIdleHandler中的非紧要的消息。
3. 如果链表中和MessageQueue.mIdleHandler都没有要执行的消息。则调用nativePollOnce(),阻塞线程,阻塞时间为nextPollTimeoutMillis
在取出消息后,会执行Looper.loop()的下面代码,将消息分发出去
最终发送给了我们重写的handleMessage中。
好了,现在我们将在UI线程中用handler分发消息走了一遍,先小结下:
客户端 :handler 向MessageQueue中发送和接受消息
服务端: Looper 从MessageQueue中不停的取数据并处理(死循环),保证多线程有序
系统: ThreadLocalMap 为每个线程记录自己的服务Looper
解决问题:
在子线程中为啥handler要放在Looper中?
由于handler本身只起发送和接收消息的作用。不具有死循环有序的存储和取出多线程消息的功能,而Looper为其提供了这些功能,即没有处理多线程发生消息的能力。
如何在子线程更新UI?????
这就简单了,我们只要把让handler(在其他线程中创建)向主线程的MessageQueue发送更新UI的消息。
有几种方法:https://blog.csdn.net/xx_dd/article/details/52888319
下面给出一个用例
在new Handler()的时候我给其传入了一个Looper(context.getMainLooper)为其绑定。
总结:
1. 上面红色字体
2. 从功能上看handler的消息处理。handler的作用为发送与接受消息,MessageQueue为存取数据和锁线程的。Message消息缓存,是一个单链表。Looper起到死循环取数据和为handler提供MessageQueue与Thread对象。ThreadLocal存储Looper对象,在handler绑定MessageQueue起到了关键作用。
3. 消息到达MessageQueue,会按执行时间顺序排放在链表中。