为什么要有Handler机制?
解决工作线程更新UI的问题。
由于在Android机制中,为了保证UI操作是线程安全的,规定只允许主线程更新Activity的UI组件。但在实际开发中存在多个线程并发操作UI组件的情况,会导致UI操作线程不安全。故采用Handler机制,当工作线程需要更新UI的时候,通过Handler通知主线程,从而在主线程中更新UI。
ps1:为什么不用锁呢?用锁会使UI的访问逻辑变得复杂,锁机制会降低UI访问的效率,锁会阻塞某些线程的执行。
Handler机制包含了什么?
重要概念
- MainThread,UI线程,子线程(工作线程)
- Handler 处理者
- Looper 循环器
- Message Queue 消息队列
- Message 消息
UI线程与工作线程
UI线程就是APP启动时,就会开启一条ActivityThread线程,称之为主线程。
工作线程,则是在操作过程中,开启的线程,如网络请求线程等。
Handler
首先看一下该类的注释,就可以知道该类的功能和用处了。
Handler使你可以发送和处理与线程MessageQueue相关联的Message和Runnable。每个实例都与一个该线程的MessageQueue向关联。当创建Handler后,就会绑定MessageQueue。从绑定之后起,它就可以将Runnable和Message传入消息队列中,并在读取到对应消息时执行他们。
从描述中,我们可以提取到几个关键信息。Handler与线程绑定,与线程的MessageQueue绑定,大概率每个线程就只能有一个MessageQueue。Handler可以发送处理两种类型的数据,Message和Runnable。(这些后面具体描述)
Looper
Looper是为了为线程提供消息循环。默认情况下,线程没有Looper,可以使用prepare()获取循环,并使用loop()方法开始处理信息,只到循环停止。
与信息循环的大部分交互的都是通过Handler类。
MessageQueue
包含Looper要发送的信息列表的低级类。消息不是直接添加到MessageQueue中,而是由与Looper关联的Handler类添加的。
从描述中可知,MessageQueue从属于Looper。
Message
就是Handler处理的消息。其中有几个比较重要的属性:
- target 发送和处理这个Message的Handler对象
- callback 在主线程执行的回调
- when 信息的传递时间
- next 指向下一条Message(链表结构)
怎么使用Handler机制?
从前文可知,Handler需要绑定MessageQueue,而MessageQueue从属于Looper,所以从创建looper开始。
消息处理
一、创建Looper对象
- 主线程 Looper.prepareMainLooper()
主线程创建的时候会调用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();
}
}
我们可以发现,这里也调用了prepare方法,我们看一下prepare的方法。
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));
}
这里出现了sThreadLocal字段,这个字段被static final修饰,说明是类共享的常量。(关于ThreadLocal具体讲解可以看上一篇文章)该常量,为每个线程都提供类Looper的副本。
static final ThreadLocal sThreadLocal = new ThreadLocal();
ps2 : 如何保证一个线程中只有一个Looper? Looper的构造方法是private,只能在prepare中调用。并且如果一个ThreadLocal获取到相应的value,说明已经创建过。会抛出异常。
ps3:prepare的方法中携带一个布尔类型参数,用于判断是否可以退出循环。子线程的都为true,意味着可以退出;主线程的为false,意味着不可以退出。
接着来看看构造函数,每个looper对象都会绑定当前线程与一个消息队列
二、使Looper开始工作
当然就是调用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;
}
......
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);
}
}
......
}
}
- 首先获得当前的Looper的消息队列 MessageQueue queue = me.mQueue;
- 然后开启死循环 for(::)
- 通过消息队列获取MessageQueue获取信息 Message msg = queue.next();(可能造成阻塞)
- 然后处理消息 msg.target.dispatchMessage(msg) 前文已知Message的target的就是发送和处理该对象的Handler。
ps4: 如何退出循环。looper调用quit方法,或者quitSafely方法后,队列就会放回msg == null,这样就可以结束循环。顾名思义,quit就是立即退出,而quitSafely则打上标志,当消息处理完全之后才结束循环。
ps5:为什么这里阻塞了?会不会影响主线程,或者影响CPU呢?这也是一个值得关注的问题,容后面分析。
三、MessageQueue读取数据
仍然还是贴出关键代码
Message next() {
......
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
// 获取当前时间 以便异步消息
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 {
// Got a message.
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
}
......
}
}
不难发现nextPollTimeoutMillis是很重要的参数。正常循环时,此参数为0。当有延迟消息,消息没准备好时,则会设置一个延迟时间,让下一次循环执行。当message为空时,此参数会设置成-1。
nativePollOnce方法是一个本地方法,也是阻塞这个消息队列的方法。当前面参数为-1时,就会使消息队列陷入等待状态。
注意这里有个同步方法,锁住类MessageQueue对象。由于处理的消息,是由其他线程传入的,为了保证线程安全,就得Synchronized。
四、Handler.dispatchMessage(msg)
从前文得知Hanler应该可以处理两类数据?为什么这里就只有Message呢?后文再说,我们先看处理方法:
当message有回调时(实际上就是前文的Runnable的类型数据),就会调用这个回调的run方法。
message.callback.run();
当msg.callback为空时,则此处会使用自定的hanlerMessage方法。
以上四步就是Handler机制处理消息的步骤了。那么Handler机制是如何上传Message的呢?
上传消息
一、重写Handler
从前文三种处理方法中,我们就可以复写出三种重写Handler方法。
- 新建Handler子类,实现handlerMessage处理方法。(对应最后一种处理)
- 匿名内部类,通过Callback实现handlerMessag。(对应倒数第二种处理)
- 在post方法中传入Runnable(对应第三种处理)
ps6: Handler要在处理该Handler(一般为主线程)的线程中创建,然后在工作调用。
ps7:这里的Runnable接口,只是作为一种声明,而不是像传统的要开启一个新的线程,切记。
ps8:子线程中可以用MainLooper去创建Handler吗? 子线程中Handler handler = new Handler(Looper.getMainLooper());,此时两者就不在一个线程中。
二、创建消息对象
- 创建Runnable对象,可以直接实现一个类,也可以直接在post方法内使用匿名内部类。
-
创建Message对象
- 创建举例
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
*
ps9: 创建用了不常见的obtain,而不是new,是因为Message自带缓冲池,避免每次都使用new重新分配内存,只有当线程池无对象时,才会new新对象。
三、发送信息到队列
对应两种类型数据,亦有两条路径:
-
post(Runnable r)
- sendMessageDelayed(getPostMessage(r), 0); getPostMessage方法中,将Runnable接口封装成了message对象,并将r设置成类msg.callback。
- sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- enqueueMessage(queue, msg, uptimeMillis); 将msg与当前的Handler绑定,并将其插入队列中
- queue.enqueueMessage(msg, uptimeMillis);这步较为关键,贴一下源码。这里使用的是单链表的增添方法,链表的好处是增删便捷,但是查询不便利,这边也很少查询的方法,故使用链表也较为合适。
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 当当前队列为空时,唤醒等待的队列,并插入头结点
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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);
}
}
return true;
}
-
sendMessage(Runnable r)
- endMessageDelayed(msg, 0);
- sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- enqueueMessage(queue, msg, uptimeMillis);
- queue.enqueueMessage(msg, uptimeMillis);
步骤基本同上。
ps10:如果需要调用延迟的话,调用postDelayed方法,最后底层还是和上面相似。
ps11:如果简单更新UI则直接使用Runnable即可(可能导致线程耦合度高)。若是要传递数据,则使用Message。
致此,怎么使用Handler机制已经讲述完成了。但是Handler机制的使用还是存在一些问题的,让我们继续探究。
Handler使用疑问
一、处理延迟消息:
1、MessageQueue.next()在取出Msg时,如果发现消息A有延迟且时间没到,会阻塞消息队列。
2、如果此时有非延迟的新消息B,会将其加入消息队列, 且处于消息A的前面,并且唤醒阻塞的消息队列。
3、唤醒后会拿出队列头部的消息B,进行处理。然后会继续因为消息A而阻塞。
4、如果达到了消息A延迟的时间,会取出消息A进行处理。
二、Looper 死循环为什么不会导致应用卡死?会消耗越来越多的资源嘛?
ActivityThread实质是只是App的入口类,而不是真正的线程。
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。
至于消耗资源,涉及到epoll机制(到IO的时候在继续讲解吧),该机制会再有数据到达时,才唤醒主线程工作。否则就让主线程休眠。
三、主线程的消息循环机制是什么?
此点不够清晰,以后详细研究。
四、Handler的内存泄漏问题。
- 当Handler是非静态内部类或匿名Handler内部类时,会持有外部应用,这样会导致一个问题。当要销毁当前Activity时,可能有消息未处理完全,Message指向了Handler,而当前Handler又持有Activity,所以当前的Activity不能被回收,可能造成内存泄漏。
-
解决办法:
- 1、存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
首先将Handler设置为静态内部类,然后持有Activity的弱引用实例。保证消息队列中所有消息都能执行。 - 2、Handler的生命周期 > 外部类的生命周期
在OnDestory方法中,清空Handle队列,并直接终止handler,mHandler.removeCallbacksAndMessages(null);
- 1、存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
五、补充
队列中的内容(无论Message还是Runnable)可以要求马上执行,延迟一定时间执行或者指定某个时刻执行,如果将他们放置在队列头,则表示具有最高有限级别,立即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。