开始
在Android中不允许多个线程同时操作一个UI控件,因为多线程同时操作可能会造成UI有不可预知的结果,所以通常我们都会用Handler机制来进行UI更新。
Handler中的几个重要角色:
- Handler
用于向MessageQueue中发送消息,和处理Looper获取到的消息,在线程中创建Handler时当前线程中必须要有Looper
- Looper
用于从MessageQueue中循环获取Message,其内部是一个无限循环
- MessageQueue
用于存储Message,在创建Looper时会同时创建MessageQueue,它的内部是一个由Message组成的单链表。
- Message
- Handler运行过程中传递的都是Message,Message中有很多属性 如:
- target 保存的发送该message的Handler,处理消息时会根据这个属性进行分派处理它的Handler
- next 保存的是下一个Messge的引用,通过它形成链表结构
- bundle 捆绑数据
- ThreadLocal
在Handler中ThreadLocal的作用就是,保存每个线程的Looper
Handler运行原理:
//Handler的构造方法
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
通过上面的源码可以看到,在创建Handler时,Handler内部会通过ThreadLocal获取当前线程的Looper,如果Looper为空就会抛出异常 "Can't create handler inside thread that has not called Looper.prepare()");
所以在创建Handler时必须要有Looper,细心的同学会发现,我们平时在主线程可以直接创建Handler并使用,那是因为UI线程在初始化到时候已经创建好了Looper,看下面的这段UI线程入口ActivityThread中的main方法源代码
public static void main(String[] args) {
......
//实例化Looper并设置到ThreadLocal里面,赋值MainLooper
Looper.prepareMainLooper();
//实例化一个ActivityThread
ActivityThread thread = new ActivityThread();
//主要是获取AMS接口 然后设置ApplicationThread 的接口 给AMS和应用程序通信
thread.attach(false);
if (sMainThreadHandler == null) {
//设置MainHandler 是一些接受生命周期的方法
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(); //开始接收消息
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在代码中我们可以看到 Looper.prepareMainLooper();这段代码就是在创建Looper。我们看看这个方法是怎么创建Looper的
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException
("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException
("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
通过上面两段就完成了Looper的创建,先是在perpareMainLooper()中调用了perpare(false)这里传入false的意思是,MessageQueue不可以中途退出,就是不可以调用MessageQueue的quit()方法,我们来看一下prepare()方法中的代码,先是判断ThreadLocal中是否存在Looper实例,如果有就抛出异常当前线程已创建了Looper,如果没有就创建一个Looper实例并set到ThreadLocal中。
ThreadLocal是什么?
在这说说我对ThreadLocal的理解,之前看了看源码也没看太懂,然后又看了一些文章,我觉得ThreadLocal就像是一个HashMap集合,只不过它key值不是我们自定义的,它的key值好像是线程名字(能代表每个线程的标识),我们用ThreadLocal.set(Object)设置值的时候,它就是用线程名字做key,用我们传递进去的对象做值。
我们看到的:
sThreadLocal.set(new Looper(quitAllowed));
应该是:
sThreadLocal.put(Thread.currentThread().getName(),new Looper(quitAllowed));
上面这段代码是我的猜测,但它用起来确实是这样的效果。
同理:
sThreadLocal.get()
应该是:
sThreadLocal.get(Thread.currentThread().getName());
所以上面prepare()方法中的sThreadLocal.get()这句话就是在获取当前线程的Looper。
ok我们继续说Looper,刚刚我们说到了创建Looper,那么我们看看创建Looper时都做了什么事情
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
上面的代码可以看出,在创建Looper时,同时创建了MessageQueue。
到这呢Handler运行机制需要的东西差不多就都准备好了,接下来就是开始运行了。
刚刚我们都知道Looper是在主线程入口main方法中通过Looper.prepareMainLooper(); 创建的,在创建完Looper后,接着又调用了Looper.loop()方法(在上面的源代码中可以看到),我们看一下loop()方法具体做了什么
public static void loop() {
// 拿到Looper
final Looper me = myLooper();
if (me == null) {
// 没调用prepare初始化Looper,报错
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 拿到消息队列
final MessageQueue queue = me.mQueue;
......
for (;;) {
// 从消息队列取出下一个信息
Message msg = queue.next();
if (msg == null) {
// 消息为空,返回
return;
}
.......
try {
// 分发消息到Handler
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
}
// 消息回收,放入缓存池
msg.recycleUnchecked();
}
1.第3行,获取通过自身myLooper()方法获取当前线程Looper对象,myLooper()就是通过ThreadLocal获取当前线程对象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
2.第4-7行,判断获取的Looper对象是否为空,为空则抛出异常,说明Looper没有创建
3.第9行,拿到跟当前线程一起的Looper中的消息队列
4.第12到27行,开始循环取出消息队列中的消息,如果有消息就通过Message中target变量指定的handler分发消息并处理
5.第26行,消息使用完进行回收,下次可以通过handler.obtainMessage()进行获取
ok,怎么获取消息和怎么处理消息我们都了解了,再说说如何把消息发送到MessageQueue消息队列中,一般我们发消息都是用handler.sendMessageDelayed(),
handler.sendMessage(),andler.sendEmptyMessage()等方法进行发送,但它们最终都会调用MessageQueue.enqueueMessage()向消息队列中发送消息,看下enqueueMessage()的源码
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
// 记录消息处理的时间
msg.when = when;
Message p = mMessages;
// 唤醒线程的标志位
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 这里三种情况:
// 1、目标消息队列是空队列
// 2、插入的消息处理时间等于0
// 3、插入的消息处理时间小于保存在消息队列头的消息处理时间
// 这三种情况都插入列表头
msg.next = p;
mMessages = msg;
// mBlocked 表示当前线程是否睡眠
needWake = mBlocked;
} else {
// 这里则说明消息处理时间大于消息列表头的处理时间,因此需要找到合适的插入位置
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 这里的循环是找到消息的插入位置
for (;;) {
prev = p;
p = p.next;
// 到链表尾,或处理时间早于p的时间
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;
}
1.第9行,开始先把这个消息队列中的消息赋值给p(由于是单链表存储形式,所以就是把第一个message对象赋值给p)
2.第12到22行,存在判断的这三种情况
(1)目标消息队列是空队列
(2)插入的消息处理时间等于0
(3)插入的消息处理时间小于保存在消息队列头的消息处理时间
存在以上三种情况,就把新传进来的Message插入到链表头部,优先处理。
3.第22到42行,把新的message插入到链表尾部,第27到38行,是通过循环移动链表指针,把指针移动到最后,p为null,prev为最后一个message时停止,然后把新的message插入到最后,prev.next = msg,msg.next = p
作者能力有限,文中若有误导内容,望指正。
文中部分代码内容借鉴https://www.jianshu.com/u/9cf1f31e1d09