Android异步消息处理机制完全解析
Handle
Handle的使用
-
Handler.sendMessage
/** * 方式1:新建Handler子类(内部类) */ // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法 class mHandler extends Handler { // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } } // 步骤2:在主线程中创建Handler实例 private Handler mhandler = new mHandler(); // 步骤3:创建所需的消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中 // 可通过sendMessage() / post() // 多线程可采用AsyncTask、继承Thread类、实现Runnable mHandler.sendMessage(msg); // 步骤5:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable /** * 方式2:匿名内部类 */ // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象 private Handler mhandler = new Handler(){ // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } }; // 步骤2:创建消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤3:在工作线程中 通过Handler发送消息到消息队列中 // 多线程可采用AsyncTask、继承Thread类、实现Runnable mHandler.sendMessage(msg); // 步骤4:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable -
Handler.post()
// 步骤1:在主线程中创建Handler实例
private Handler mhandler = new mHandler();
// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
// 需传入1个Runnable对象
mHandler.post(new Runnable() {
@Override
public void run() {
... // 需执行的UI操作
}
});
// 步骤3:开启工作线程(同时启动了Handler)
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
源码解析
Handle和Looper
先看下在我们实例化Handler的时候,Handler的构造方法中都做了什么
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
从上面,我们可以知道,在调用Looper.myLooper()之前必须要先调用Looper.prepare()方法,现在来看下prepare方法中的内容,如下
public static void prepare() {
prepare(true);
}
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));
}
可以看到,prepare()方法调用了prepare(boolean quitAllowed)方法,prepare(boolean quitAllowed) 方法中则是实例化了一个Looper,然后将Looper设置进sThreadLocal中,到了这里就有必要了解一下ThreadLocalle。可以参考之前Java并发编程总结中的ThreadLocal。所以Looper.prepare方法就是将Looper与当前线程进行绑定(当前线程就是调用Looper.prepare方法的线程)。
ThreadLocal,线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal可以让每个线程拥有一个属于自己的变量的副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离。
在调用Looper.myLooper方法之前必须必须已经调用了Looper.prepare方法,即在实例化Handler之前就要调用Looper.prepare方法,但是我们平常在主线程中使用Handler的时候并没有调用Looper.prepare方法呀!这是为什么呢?
其实,在主线程中Android系统已经帮我们调用了Looper.prepare方法,可以看下ActivityThread类中的main方法,代码如下
public static void main(String[] args) {
...
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();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这句话的实质就是调用了Looper的prepare方法,代码如下
@Deprecated
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
我们再看一下Handle的构造函数初始化变量中
mQueue = mLooper.mQueue;
这句代码。这句代码就是拿到Looper中的mQueue这个成员变量,然后再赋值给Handler中的mQueue,下面看下Looper中的代码
final MessageQueue mQueue;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
同过上面的代码,我们可以知道mQueue就是MessageQueue,在我们调用Looper.prepare方法时就将mQueue实例化了。消息队列是一个存放消息的容器,先进先出FIFO的数据结构。这个消息队列我们后续再说。
发送消息
既然创建了一个Handle对象,那我们怎么发送消息呢?
首先需要创建一个Message对象。
常见创建Message有三种方式
-
Message msg = new Message();
每次需要Message对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间
-
Message msg =Message.obtain();
obtainMessage能避免重复Message创建对象。它先判断消息池是不是为空,如果非空的话就从消息池表头的Message取走,再把表头指向 next。如果消息池为空的话说明还没有Message被放进去,那么就new出来一个Message对象。消息池使用 Message 链表结构实现,消息池默认最大值 50。消息在loop中被handler分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。
- 享元模式
-
Message msg = handler.obtainMessage();
其内部也是调用的obtain()方法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
可以看到,上面所有的消息最终都是进入sendMessageAtTime
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
//其中 mQueue 是消息队列,从 Looper 中获取的
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//调用 enqueueMessage 方法
return enqueueMessage(queue, msg, uptimeMillis);
}
我们看一下MessageQueue enqueueMessage
boolean enqueueMessage(Message msg, long when) {
//每一个 Message 必须有一个 target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
//正在退出时,回收 msg,加入到消息池
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;
//p 为 null(代表 MessageQueue 没有消息) 或者 msg 的触发时间是队列中 最早的, 则进入该该分支
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 {
//将消息按时间顺序插入到 MessageQueue。一般地,不需要唤醒事件队列, 除非 //消息队头存在 barrier,并且同时 Message 是队列中最早的异步消息。
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;
}
MessageQueue 是按照 Message 触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找 到消息应该插入的合适位置,以保证所有消息的时间顺序。
获取消息
那怎么获取消息呢?
当发送了消息后,在 MessageQueue 维护了消息队列,然后在 Looper 中通过 loop() 方法,不断地获取消息。上面对 loop()方法进行了介绍,其中最重要的是调用 了 queue.next()方法,通过该方法来提取下一条信息。下面我们来看一下 next() 方法的具体流程。
Message next() {
final long ptr = mPtr;
//当消息循环已经退出,则直接返回
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待 nextPollTimeoutMillis 时长,或者消息队列被唤醒,都会返回
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) {
//当消息 Handler 为空时,查询 MessageQueue 中的下一条异步消息 m sg,为空则退出循环。
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);
//设置消息的使用状态,即 flags |= FLAG_IN_USE
msg.markInUse();
return msg;
}
} else {
// 没有更多消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
......
}
}
nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一个消息到来前, 还需要等待的时长;当 nextPollTimeoutMillis = -1 时,表示消息队列中无消息, 会一直等待下去。 可以看出 next()方法根据消息的触发时间,获取下一条需要执行的消息,队列中 消息为空时,则会进行阻塞操作。
分发消息
在 loop()方法中,获取到下一条消息后,执行 msg.target.dispatchMessage(msg), 来分发消息到目标 Handler 对象。 下面就来具体看下 dispatchMessage(msg)方法的执行流程。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
//当 Message 存在回调方法,回调 msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//当 Handler 存在 Callback 成员变量时,回调方法 handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler 自身的回调方法 handleMessage()
handleMessage(msg);
}
}
分发消息流程:
当 Message 的 msg.callback 不为空时,则回调方法 msg.callback.run();
当 Handler 的 mCallback 不为空时,则回调方法 mCallback.handleMessage(msg);
最后调用 Handler 自身的回调方法 handleMessage(),该方法默认为空,Handler
子类通过覆写该方法来完成具体的逻辑。
消息分发的优先级:
Message 的回调方法:message.callback.run(),优先级最高;
Handle总结
在使用Handle之前必须调用Looper.prepare,这句代码的工作就是将Looper和当前线程进行绑定。在实例化Handle的时候,通过Looper.myLooper方法获取Looper,然后再获得Looper中的MessageQueue。子线程调用Handle的sendMessage或者是post Runable都是将Message放进MessageQueue中,然后调用Looper.loop方法来从MessageQueue取出Message,在取到Message的时候,执行msg.target.dispatchMessage方法,这个方法内部就是handle的handleMessage方法。
从四个方面看Handler、Message、MessageQueue 和 Looper
- Handler:负责消息的发送和处理
- Message:消息对象,类似于链表的一个结点;
- MessageQueue:消息队列,用于存放消息对象的数据结构;
- Looper:消息队列的处理者(用于轮询消息队列的消息对象)
Handler发送消息时调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法 如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着调用handlerMessage()。
Handler postDelayed的原理
- 消息是通过MessageQueen中的enqueueMessage()方法加入消息队列中的,并且它在放入中就进行好排序,链表头的延迟时间小,尾部延迟时间最大
- Looper.loop()通过MessageQueue中的next()去取消息
- next()中如果当前链表头部消息是延迟消息,则根据延迟时间进行消息队列会阻塞,不返回给Looper message,知道时间到了,返回给message
- 如果在阻塞中有新的消息插入到链表头部则唤醒线程
- Looper将新消息交给回调给handler中的handleMessage后,继续调用MessageQueen的next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞
handler.postDelay() 的实现 是通过MessageQueue中执行时间顺序排列,消息队列阻塞,和唤醒的方式结合实现的。
线程同步
怎么保证线程同步问题?
Handler机制里面最主要的类MessageQueue,这个类就是所有消息的存储仓库,在这个仓库中,我们如何的管理好消息,这个就是一个关键点了。消息管理就2点:1)消息入库(enqueueMessage),2)消息出库(next),所以这两个接口是确保线程安全的主要档口。
首先我们看一下enqueueMessage源码和next源码,都加了synchronized内置锁。说明的是对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,然而,在我们的Handler里
面,一个线程是对应着一个唯一的Looper对象,而Looper中又只有一个唯一的MessageQueue。所以,我们主线程就只有一个MessageQueue对象,也就是说,所有的子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱。
而next函数可能会有疑问:我从线程里面取消息,而且每次都是队列的头部取,那么它加锁是不是没有意义呢?答案是否定的,我们必须要在next里面加锁,因为,这样由于synchronized(this)作用范围是所有 this正在访问的代码块都会有保护作用,也就是它可以保证 next函数和 enqueueMessage函数能够实现互斥。这样才能真正的保证多线程访问的时候messagequeue的有序进行。
runWithScissors
-
Handler 的 runWithScissors() 可实现 A 线程阻塞等待 B 线程处理完消息后再继续执行的功能,它为什么被标记为 hide?存在什么问题?原因是什么?
- 实现:将 Runnable 包装为 BlockingRunnable,其内通过 synchronized + wait 进入等待,待 r 执行完后,调用 notifyAll() 唤醒等待队列的子线程,子线程继续执行;
- 问题:在子线程 Looper 中使用,可能导致 A 线程进入 wait 等待,而永远得不到被 notify 唤醒;
- 原因:子线程 Looper 允许退出,若包装的 BlockingRunnable 被执行前,MessageQueue 退出,则该 runnable 永远不会被执行,则会导致 A 线程一直处于 wait 等待,永远不会被 notify 唤醒;
epoll机制
-
是什么?
epoll是Linux内核的可扩展I/O事件通知机制。于Linux 2.5.44首度登场,它设计目的旨在取代既有POSIX select(2)与poll(2)系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能。epoll 实现的功能与 poll 类似,都是监听多个文件描述符上的事件。 epoll 通过使用红黑树(RB-tree)搜索被监控的文件描述符(file descriptor)。在 epoll 实例上注册事件时,epoll 会将该事件添加到 epoll 实例的红黑树上并注册一个回调函数,当事件发生时会将事件添加到就绪链表中。
-
为什么使用
epoll是一种I/O事件通知机制(事件驱动的,犹如观察者模式)。管道机制需要一端写数据另一端才能读数据,但在实践中,我们往往不能这样无尽地等待,而是希望有一个监听,你什么时候写再通知我去读。
在epoll出现之前,也有诸如select和poll这样的监听机制,但是效率比较低,有些需要无差别遍历FD,虽然也是非阻塞,但唤醒后要轮询I/O,或者是有FD监听数量上限等缺点,具体此处不赘述。
总之,epoll解决了这些问题,实现了高性能的I/O多路复用,还使用mmap加速内核与用户空间的消息传递。
需要兼顾 Native 层的消息,消息可能来自底层硬件;如果只考虑 Java 层,notify/wait 即可实现;
epoll内部实现还是比较复杂的,用红黑树结构管理fd,双向链表结构管理回调的事件,具体可以参考文末链接。简单的讲一下原理,
epoll_ctl函数会把传入的fd和event进行存储,并与相应的设备驱动程序建立关系,当相应的事件发生时,就会调用内部的回调函数将事件添加到链表中,最终通知线程唤醒,epoll_wait得以返回。没有事件发生时,epoll_wait就是挂起状态。 -
三个函数
//调用epoll_create构建一个epoll的句柄 int epoll_create(int size); //调用epoll_ctl 注册一个事件的监听,这个事件一般是文件描述符的数据是否发生变化 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //调用epoll_wait 阻塞当前的循环,直到监听到数据流发生变化,就释放阻塞进行下一步 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
同步屏障
定义
屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?
MessageQueue#postSyncBarrier()
源码解析
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//从消息池中获取Message
final Message msg = Message.obtain();
msg.markInUse();
//初始化Message对象的时候,并没有给target赋值,因此 target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
//如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
//根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到,Message 对象初始化的时候并没有给 target 赋值,因此, target == null 的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条 target == null 的消息就进入了消息队列。那么,开启同步屏障后,所谓的异步消息又是如何被处理的呢?
如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器 Looper#loop() 中,而 loop() 循环中会调用 MessageQueue#next() 从消息队列中进行取消息。
Message next() {
// 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
// 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
// 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
// 如果期间有程序唤醒会立即返回。
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
//next()也是一个无限循环
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) {
// 如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,
//场景如常用的postDelay
if (now < msg.when) {
//计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
//表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取到消息
mBlocked = false;
//链表操作,获取msg并且删除该节点
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复位
nextPollTimeoutMillis = -1;
}
...
}
}
从上面可以看出,当消息队列开启同步屏障的时候(即标识为 msg.target == null ),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。
- 那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用 removeSyncBarrier() 。
同步屏障应用场景
Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。
比如,在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了
ViewRootImpl#scheduleTraversals() ,如下:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
postCallback() 最终走到了 ChoreographerpostCallbackDelayedInternal() :
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//开启异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。
最后,当要移除同步屏障的时候需要调用 ViewRootImpl#unscheduleTraversals() 。
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
同步屏障总结
同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即 为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。
常见问题
1. 子线程中能不能直接new一个Handler,为什么主线程可以?
主线程的Looper第一次调用loop方法,什么时候,哪个类不能,因为Handler 的构造方法中,会通过Looper.myLooper()获取looper对象,如果为空,则抛出异常,主线程则因为已在入口处ActivityThread的main方法中通过 Looper.prepareMainLooper()获取到这个对象,并通过 Looper.loop()开启循环,在子线程中若要使用handler,可先通过Loop.prepare获取到looper对象,并使用Looper.loop()开启循环
2. Handler导致的内存泄露原因及其解决方案
原因:
- Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用
- 我们在Activity中使用非静态内部类初始化了一个Handler,此Handler就会持有当前Activity的引用。
- 我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们Activity页面关闭之后,存在引用关系:"未被处理 / 正处理的消息 -> Handler实例 -> 外部类",如果在Handler消息队列 还有未处理的消息 / 正在处理消息时 导致Activity不会被回收,从而造成内存泄漏
解决方案:
- 将Handler的子类设置成 静态内部类,使用WeakReference弱引用持有Activity实例
- 当外部类结束生命周期时,清空Handler内消息队列
3. Handler的post与sendMessage的区别和应用场景
-
源码
-
sendMessage
sendMessage-sendMessageAtTime-enqueueMessage。
-
post
sendMessage-getPostMessage-sendMessageAtTime-enqueueMessage getPostMessage会先生成一个Messgae,并且把runnable赋值给message的callback
-
-
Looper->dispatchMessage处理时
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }dispatchMessage方法中直接执行post中的runnable方法。
而sendMessage中如果mCallback不为null就会调用mCallback.handleMessage(msg)方法,如果handler内的callback不为空,执行mCallback.handleMessage(msg)这个处理消息并判断返回是否为true,如果返回true,消息处理结束,如果返回false,handleMessage(msg)处理。否则会直接调用handleMessage方法。
-
总结
post方法和handleMessage方法的不同在于,区别就是调用post方法的消息是在post传递的Runnable对象的run方法中处理,而调用sendMessage方法需要重写handleMessage方法或者给handler设置callback,在callback的handleMessage中处理并返回true
4. Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
MessageQueue#next 在没有消息的时候会阻塞,如何恢复?
他不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态,所以他不会阻塞他。
5. ANR和Handle的联系?
Handler是线程间通讯的机制,Android中,网络访问、文件处理等耗时操作必须放到子线程中去执行,否则将会造成ANR异常。 ANR异常:Application Not Response 应用程序无响应 产生ANR异常的原因:在主线程执行了耗时操作,对Activity来说,主线程阻塞5秒将造成ANR异常,对BroadcastReceiver来说,主线程阻塞10秒将会造成ANR异常。 解决ANR异常的方法:耗时操作都在子线程中去执行 但是,Android不允许在子线程去修改UI,可我们又有在子线程去修改UI的需求,因此需要借助Handler。
6. 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
子线程中维护的looper在无消息时调用quit,可以结束循环.loop()是一个死循环,想要退出,必须msg == null
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.recycleUnchecked();
}
}
MessageQueue中的quit如下
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
- prepare()
- loop()
- quit()
6. Looper如何与 Thread 关联的
Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper#prepare() 方法 Looper 中有一个ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 get 和 set 方法来赋值和取值。 由于 ThreadLocal是与线程绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了
7. Looper的quit和quitSafely有什么区别
public void quitSafely() {
mQueue.quit(true);
}
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
- 当我们调用Looper的quit方法时,实际上运行了MessageQueue中的removeAllMessagesLocked方法。该方法的作用是把MessageQueue消息池中全部的消息全部清空,不管是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的须要延迟运行的消息)还是非延迟消息。
- 当我们调用Looper的quitSafely方法时,实际上运行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就能够看出。该方法仅仅会清空MessageQueue消息池中全部的延迟消息。并将消息池中全部的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发全部的非延迟消息。
- 不管是调用了quit方法还是quitSafely方法仅仅会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了。这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,由于消息队列已经退出了。
8. MessageQueue没有消息时候会怎样?阻塞之后怎么唤醒呢?说说pipe/epoll机制?
- 当消息不可用或者没有消息的时候就会阻塞在
next方法,而阻塞的办法是通过pipe/epoll机制。 - epoll机制是一种IO多路复用的机制,具体逻辑就是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的。在Android中,会创建一个Linux管道(Pipe)来处理阻塞和唤醒。
- 当消息队列为空,管道的读端等待管道中有新内容可读,就会通过
epoll机制进入阻塞状态。 - 当有消息要处理,就会通过管道的写端写入内容,唤醒主线程。
IdeaHandle
常见问题
-
IdleHandler 有什么用?
- IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
- 当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
-
MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?
- 不是必须;
- IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler
-
当 mIdleHanders 一直不为空时,为什么不会进入死循环?
- 只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
- pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
-
是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?
- 不建议;
- IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;
-
IdleHandler 的 queueIdle() 运行在那个线程?
- 陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
- 子线程一样可以构造 Looper,并添加 IdleHandler;
- 在Activity启动时会执行到handleResumeActivity这个Message中,在这个消息中首先会执行OnResume,接着会将DecorView添加到WindowManager中从而创建出ViewRootImpl,最后ViewRootImpl执行performTraversals开始View的测绘。从这里可以看出如果我们在OnResume中进行一些操作的话是会影响Activity启动速度的,所以我们可以通过IdleHandler将原先在OnResume中执行的任务放到IdleHandler中去处理从而加快页面展示。
HandleThread
背景
HandlerThread是Thread的子类,严格意义上来说就是一个线程,只是它在自己的线程里面帮我们创建了Looper。
-
方便使用:
a. 方便初始化
b. 方便获取线程looper
-
保证了线程安全
我们一般在Thread里面 线程Looper进行初始化的代码里面,必须要对Looper.prepare(),同时要调用Loop。
@Override public void run() { Looper.prepare(); Looper.loop(); }而我们要使用子线程中的Looper的方式是怎样的呢?看下面的代码
Thread thread = new Thread(new Runnable() { Looper looper; @Override public void run() { Looper.prepare(); looper =Looper.myLooper(); Looper.loop(); } public Looper getLooper() { return looper; } }); thread.start(); Handler handler = new Handler(thread.getLooper());上面这段代码存在以下问题:
-
在初始化子线程的handler的时候,我们无法将子线程的looper传值给Handler,解决办法有如下办法:
- 可以将Handler的初始化放到 Thread里面进行
- 可以创建一个独立的类继承Thread,然后,通过类的对象获取。
这两种办法都可以,但是,这个工作 HandlerThread帮我们完成了
-
依据多线程的工作原理,我们在上面的代码中,调用 thread.getLooper()的时候,此时的looper可能还没有初始化,此时是不是可能会挂掉呢
-
特点
- HandleThread本质上本质线程类,继承Thread
- 内部有looper对象,run方法中开启了loop循环
- 通过获取looper对象传递给Handler对象,可以在handleMessage中执行异步任务;
- 优点是不会有堵塞,减少了性能的消耗,缺点是不能同时执行多任务的处理,需要进行等待处理,处理的效率比较低;
- 和线程池并发不同,它是串行队列,它背后只有一个线程;它的run方法是一个无限循环,明确不需要使用时,需要调用quit或quitSafely退出;
IntentService
定义
- 是执行并处理异步请求的一个特殊的service,由于它是一个Service,它的优先级比普通的后台线程要高,不易被系统杀死,它适合执行一些优先级较高的后台任务。
- 内部封装了handlerthread和handler,利用handlerThread处理耗时操作,启动方式和传统的service启动方式一样,同时,任务执行完毕后,它会自动的停止,不需要我们手动调用stopserivice方法。
- 它可以启动多次,每一个耗时的操作都会以工作队列的方式在IntentService得onHandleIntent回掉方法中执行,并且,每次只会执行一个工作线程,执行完毕后才会执行第二个。它是串行的。
使用方法
自定义一个类继承IntentService,实现onHandlerIntent()方法,该方法是在子线程中执行,所以可以进行一些耗时的操作;
class MyIntentService extends IntentService{
public MyIntentService(String name) {
super(name);
}
/**
* 工作线程中执行
*/
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String task_action = intent.getStringExtra("task_action");
SystemClock.sleep(3000);//模拟耗时任务
Log.d("MyIntentService", "handle task:" + task_action);
}
}
启动方式和传统一样:
Intent service=new Intent(this,MyIntentService.class);
service.putExtra("task_action","task1");
startService(service);
service.putExtra("task_action","task2");
startActivity(service);
源码分析
封装了HandlerThread和Hanler的异步框架;
handler发送消息,处理消息时回掉onHandleIntent()方法;创建hanlder需要提供looper,looper来源于HandlerThread,在HandlerThread这个子线程构建了一个消息循环系统,handler处理消息的线程就是创建handler的线程,即handlerThread这个线程;