异步处理机制的作用
提到Android异步处理机制,我们首先会想到Handler,而大多数Android初学者对于Handler的作用仅局限于子线程如何更新UI这一方面。其实Handler能做的事情远不止于此,它贯穿于Android应用的整个生命周期,如果没有Handler,我们编写的应用就无法正常运行。总的来说,它的作用体现在两大方面:
- 处理延时任务(告诉app在将来的某个时间执行某个任务)
- 线程之间通信(子线程更新UI就是其中的一个应用)
角色分析与流程描述
与Android异步处理机制相关较为重要的类有以下6个:
- Handler:负责发送、处理消息,提供了send、post和handleMessage等方法
- Message:在线程中要传递的信息(链表结构),其中有what、arg、data等一些信息
- MessageQueue:消息队列,Handler发送的消息就放在队列中。
- Looper:负责管理消息队列,提供了loop方法不断地取出消息
- Thread:线程,会调用Looper的loop方法
- ThreadLocal:类似于HashMap,用键值对存储的一种数据结构,保证一个线程只有一个Looper
都说程序来源于生活,Android异步处理机制自然可以用生活中一个例子来说明:
回想我们坐火车、飞机之前过安检的流程:旅客要把自己的行李放到安检机里面,之后走到后面等待行李从机器中出来。这个例子中的旅客就相当于程序中的Handler,行李对应于Message,而安检机自然就对应于MessageQueue了。然而行李是怎么出来的呢? 是传送带送出来的,这里的传送带就相当于Looper,那么传送带为什么会动呢?因为有电动机为其提供了动力,在程序中,为Looper提供动力的就是线程了。在线程中会调用Looper的loop方法,而loop方法是一个死循环,导致消息队列中的消息源源不断地被取出来进行处理。
例子理解了之后,还有几个问题需要我们思考的。
首先,旅客不止一个所以安检机中行李也有许多,那么旅客怎么确定自己要取的到底是哪一个呢?这个问题听上去好像有点蠢,难道自己的行李自己还不认识吗?在现实生活中确实能认出来,不过在程序中怎么让Handler识别出自己发送的消息呢?针对这个问题我们可以想到,在发送的时候给消息做一个标记,标记出发送消息的Handler,那么在处理消息的时候就可以根据这个标记来找到应该要处理的Handler对象了,这一点在后面的源码分析中就会看到。
源码分析
Message
首先来看一下Message类中包含的一些重要字段:
//用来识别消息的字段
public int what;
//Message中可以携带的整形数据arg1和arg2
public int arg1;
public int arg1;
//Message中可携带的对象
public Object obj;
//记录是哪个Handler发送的消息
Handler target;
//指针,指向下一个Message
Message next;
//缓存池
private static Message sPool;
//当前缓存池的大小
private static int sPoolSize = 0;
//缓存池最大容量
private static final int MAX_POOL_SIZE = 50;
接下来看一下它的构造方法:
public Message() {
}
由源码中看到构造方法中什么都没有,然而在开发中并不建议通过new的方式创建出Message对象。Message中由一个static类型的sPool字段,它代表了缓存池,也就是说当一个消息被处理掉之后并不会直接销毁,而是放在缓存池中,下次再需要时可以通过Message提供的obtain方法从池中得到,这样一来便减少了内存的开销,实现了资源的复用。
obtain方法有很多重载,这里只分析无参的重载,其他方法的原理是类似的:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
这个方法很简单,如果池子不为null则会取出池中第一个Message(头节点)并将sPoolSize减一,否则返回一个new出来的Message对象。
准备工作
在发送消息之前,要进行一些准备工作(初始化操作)。这一点很好理解:想坐飞机要通过安检,因此首先要有一个安检机,所以在发消息之前要确保有一个Looper对象(MessageQueue在Looper中创建)。我们来看一下Looper的构造函数:
private Looper(boolean quitAllowed) {
//创建出消息队列
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
诶?居然是私有的,这就意味着我们不能在外界new出Looper对象。既然构造函数被私有化,则说明Looper一定提供了一个public的方法来获取对象,这个方法就是myLooper:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在方法中是通过ThreadLocal的get方法来获取到Looper对象的,那么这个对象是通过哪个方法被创建出来的呢?答案就是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));
}
由上述代码我们发现,当调用了Looper.prepare()之后会new出一个Looper对象并存到ThreadLocal中,接下来我们点开ThreadLocal的set方法瞧瞧:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
简单来说,ThreadLocal跟HashMap类似,是一个键值对的存储结构,首先根据当前线程获取到Map,Map存储数据时是以ThreadLocal自身为key,传进来的参数为value。这样我们便得出了一个结论:每一个线程最多只能有一个Looper对象,而Looper中维护了一个MessageQueue对象,因此每个线程也只能有一个MessageQueue对象。这是因为在prepare方法中首先会判断,如果当前线程从ThreadLocal获取到的Looper对象不为null(说明此前已经调用过prepare方法创建Looper对象了)就会抛出异常。
发送消息
发送消息是通过Handler提供的一系列send方法完成的,那么首先要有Handler对象才能发送,所以我们先来看一下Handler的构造方法:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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;
}
Handler的构造方法中首先要通过Looper.mLooper方法获取到Looper对象,并通过Looper对象获取到MessageQueue对象给mQueue赋值。这里我们需要注意:在哪个线程创建的Handler对象,这个Handler对象中的mLooper引用指向的就是哪个线程中唯一的那个Looper对象。 如果该线程还没有Looper对象,new出Handler时就会抛出异常:Can’t create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare(),这就是上一小节准备工作的作用。
有了Handler对象之后,就可以通过它提供的一系列send、post方法来发送消息了。不管用哪一个方法来发送消息,最终都会调用Hander的enqueueMessage方法,而Hander的enqueueMessage方法中又会调用MessageQueue的enqueueMessage方法,整个过程可以用下图表示:
Handler的enqueueMessage方法如下所示:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到不管是哪个方法,最终都会调用MessageQueue的enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//如果目标为null抛异常
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
//如果已经在使用抛异常
throw new IllegalStateException(msg + " This message is already in use.");
}
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;
//如果队列位null或者message的when=0或者message的when小于队列第一个元素的when
if (p == null || when == 0 || when < p.when) {
//此时应该将新消息插入到链表首部
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;
//找到p和prev,其中p为message的下一个元素,prev为message的上一个元素
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//将message插入到链表的对应位置
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;
}
如果对数据结构中的链表比较熟悉,那么上述代码应该不难理解。总的思路就是将要的Message按要执行的时间顺序插入到链表中:
这也就说明,不管调用哪个方法发送消息,最终都是向消息队列中添加这条消息。那么问题来了,应用运行中可能存在很多个线程,每一个线程都有一个消息队列,到底往哪个队列中添加消息呢?
为了回答这个问题,我们要回想一下Handler中的queue是从哪里来的?在Handler的构造函数中是通过myLooper方法获得Looper对象,再通过Looper对象获取到queue的。这就说明:不管在哪个线程中通过Hander对象发送消息,最终都是向创建出Handler的线程中的MessageQueue队列中添加。
处理消息
上一小节分析了发送消息的流程,得出的结论就是每次发送消息都是将其添加到消息队列中。俗话说得好,有进就有出,那么进到队列中的消息是怎么取出并被Handler处理的呢?
MessageQueue提供了enqueueMessage方法添加消息,也提供了next方法取出一条消息:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
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) {
// 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) {
//计算出要睡眠的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else { //该消息携带的Handler对象(target)不为null
//链表头节点出队
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回消息
return msg;
}
} else {
//如果消息队列中没有消息,将nextPollTimeoutMillis置为-1,-1表示无线等待
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
……
// 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;
}
}
那么谁调用了MessageQueue的next方法呢?当然就是Looper了,Looper的loop方法如下:
public static void loop() {
//获取到当前线程下的Looper对象
final Looper me = myLooper();
if (me == null) {
//如果为null则抛异常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取到me的消息队列
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 {
//从消息中找到对应的Handler进行处理
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
……
msg.recycleUnchecked();
}
}
上述代码中会调用MessageQueue的next获取到下一条要处理的消息,之后通过msg.target.dispatchMessage(msg)语句找到对应的Handler进行处理:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//没设置Callback就会调用handleMessage方法进行处理
handleMessage(msg);
}
}
停止Looper
如果在线程中调用了Looper.loop,那么这个线程就会无限循环的从消息队列中取出消息,这样一来线程不会终止,资源也就不会被释放。如何停止循环呢?回顾一下loop方法中死循环的代码:
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
……
}
可以发现,如果消息队列的next方法返回值为null则会跳出循环,那么next方法什么情况返回null呢?看一下next方法中死循环中的一段代码:
if (mQuitting) {
dispose();
return null;
}
如果mQuitting变量为true则会返回null,那么什么情况会为true呢?细心一点就会发现MessageQueue提供了一个quit方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
//主线程不能停止循环!
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
//将mQuitting置为true
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
//唤醒线程
nativeWake(mPtr);
}
}
方法中将 mQuitting设为了true,这样一来就停掉了循环的过程。我们再留意一下quit的第一个if判断,里面抛出了一个异常提示Main thread not allowed to quit,也就是说只能通过quit停掉子线程中的loop循环,那么为什么主线程中的loop不能停掉呢?这个问题就要从ActicityThread的源码中获得答案了。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
//系统为主线程准备了Looper
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//为主线程创建Handler
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);
//系统为主线程开启了loop循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在代码中我们发现,ActivityThread的主方法中调用了Looper.prepareMainLooper()和Looper.loop()方法,所以在主线程中可以直接创建Handler而不用手动做准备工作。此外,还可以看到主线程中创建了一个sMainThreadHandler,这个Handler不是之前分析过的Handler,而是ActivityThread中的一个内部类:
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
……
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
handleConfigurationChanged((Configuration) msg.obj);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
cci.context.performFinalCleanup(cci.who, cci.what);
break;
……
}
}
可以看到,主线程中默认创建的Handler起到了至关重要的作用:开启、退出应用,创建服务,广播等等。所以说如果没有Handler,Android应用就无法正常运行。
阻塞和唤醒机制
消息队列的阻塞可能有两种情况,一是目前还没有到队列中第一个消息该被处理的时间,二是队列中已经没有消息了,这两种情况都是由一个nativePollOnce方法来完成的,我们来看看它的定义:
private native void nativePollOnce(long ptr, int timeoutMillis);
发现它是一个native方法,这意味着阻塞是通过Linux层来完成的,其中ptr参数代表着当前线程的指针,timeoutMillis参数代表要阻塞的时间,timeoutMillis为-1时代表一直阻塞。在处理消息一小节的源码中可以看到,如果队列中有消息就计算出当前时间距离消息要执行的时间的差值,没有就将timeoutMillis置为-1。
根据上面的分析,如果消息队列中没有消息线程就会一直阻塞,那么应该如何唤醒呢?首先我们能想到的是,如果给队列中添加一个消息,线程应该被唤醒。所以我们回顾一下MessageQueue的enqueueMessage是不是这样做的:
boolean enqueueMessage(Message msg, long when){
synchronized (this){
……
if (needWake) {
nativeWake(mPtr);
}
}
}
在同步代码块的最后会调用nativeWake方法来唤醒线程,这个nativeWake方法同样是一个native方法(跟nativePollOnce对应):
private native static void nativeWake(long ptr);
除了添加消息之外,在上一小节中提到的quit方法中也会唤醒线程,这一点是必然的,如果不唤醒的话,子线程loop死循环就不会跳出,线程也就不会终止了。
Callback
回顾一下Handler中处理消息的dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果msg或者handler中的callback都为null才会执行handler自己的handleMessage方法,如果不为null就会执行它们对应的callback方法。总结一下,callback被调用的情况大致有3种:
- Handler的post()方法
- View的post()方法
- Activity的runOnUiThread()方法
首先看看Handler中的post()方法:
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
post方法的本质还是调用sendMessageDelayed来发送一条消息,只不过在getPostMessage种将线程封装成了Message中的callback,其本质和发送一条正常的消息是一样的。
再来看看runOnUiThread方法:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果当前线程不是主线程,就会post一条消息,否则直接在主线程种执行action的run方法。
最后看看View的post方法:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
内部也是调用了Handler的post方法。
捋一下思路
目前为止异步处理的流程基本已经分析完了,我们来从大的方向上重新捋一下思路:在一个线程中想要通过Handler发送消息,首先要调用Looper.prepare来使当前线程具备一个Looper(只能有一个),在Looper的构建中会创建一个MessageQueue,这样一来每一个线程对应了唯一的Looper,也就对应唯一的MessageQueue。当创建一个Handler对象时,其内部会调用mLooper = Looper.myLooper()来让创建出的Handler对象获取到在创建线程中的Looper,再通过这个Looper来获取到该线程中的MessageQueue,示意图如下:
总的来说,一个线程中可以创建许多Handler,然而这些Handler对象持有的Looer、MessageQueue引用指向的都是相同的对象(线程中唯一的的)。这样一来,在其他线程中用这些Handler对象去发送消息(发出的消息持有发消息的Handler对象的引用),发出去的消息最终都是被放到了创建Handler线程中对应那个MessageQueue中。而创建Handler的线程中通过Looper.loop死循环不断地从消息队列中取出消息,这样便实现了线程之间的通信;由于对消息的入队和出队操作都是加了锁的,因此便保证了通信的安全性。
同步屏障机制
Handler 中存在着一种叫做同步屏障的机制,它可以实现异步消息优先执行的功能,为了说明这个机制,我们先要从Message消息类型说起。Message可以分为3种:同步消息、异步消息和屏障消息。我们通常使用的消息都是同步消息,而屏障消息在消息队列种就相当于一堵墙,墙下面有一个洞,这样一来在挡在墙后面的普通消息(同步消息)就无法通过(被Handler处理),异步消息可以通过。
Android 系统中的 UI 更新相关的消息属于异步消息,需要优先处理。简单说,如果在绘制UI这条消息前面有一条非常耗时的消息,那就会导致 UI 不能按时绘制,导致卡顿掉帧。同步消息屏障就可以用来保证 UI 绘制的优先性:
Message提供了以下方法设置和判断异步消息:
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
什么样的消息是屏障消息呢?屏障消息的特点是携带的Handler对象target为null,MessageQueue提供了一个postSyncBarrier方法来发送一条屏障消息:
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
上述代码非常简单,就是按时间顺序将这条消息插入到消息队列中,值得注意的是这条消息是没有target的。如果想移除屏障,可以调用MessageQueue的removeSyncBarrier方法:
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
方法很简单,就是通过插入同步屏障时返回的token 来移除屏障。接着我们来回顾一下MessageQueue的next方法(保留了关键代码):
Message next() {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) { //消息队列首部遇到屏障
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
}
if (msg != null) {
……
}
……
}
}
可以看到,在如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if下面的代码。可以看到通过这种方式就挡住了所有的普通消息。
回顾以下同步消息的发送,我们会得到以下结论:
- 屏障消息和非屏障消息的区别在于屏障消息没有tartget,这一点很好理解。因为屏障消息的作用仅仅是阻挡同步消息,它本身不需要被Handler处理。
- 屏障消息只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,所以通常情况下不需要我们发送屏障消息。
- 插入屏障消息不会唤醒消息队列所在线程,因为屏障消息不需要被处理。
常见问题的解答
(1)一个线程可以有几个Handler?
不管在哪个线程都可以通过Handler handler = new Handler()的方式创建Handler对象,只要内存够用创建多少都是可以的。
(2)为什么一个线程最多只能有一个Looper?是如何保证的?
在Looper.prepare方法的源码中是通过ThreadLocal来创建Looper对象的。其过程是:首先获取到当前的线程,再由这个线程获取到线程中的ThreadLocalMap对象,最后通过map的set方法将new出的Looper以键值对的形式存到map中。key为ThreadLocal,value为Looper,一个key只能对应一个value,而Looper中的ThreadLocal对象是由static和final修饰的,因此具有唯一性。此外,prepare方法首先会判断如果当前线程已经具有了一个Looper,那么就会抛出异常:Only one Looper may be created per thread。
(3)使用Handler时可能会发生内存泄露,原因是什么?
这个问题要从Java非静态内部类说起,非静态内部类中会隐式的持有外部类的对象,而我们通常是通过匿名内部类的方式使用Handler的,所以Handler中会持有Activity的引用。如果使用Handler发送了一条延时任务,在处理时间未到时如果Activity被销毁掉(onDestory方法执行),此时Activity对象应该从内存中被清除,然而Activity还被Handler持有引用,因此JVM不会清理掉Activity的内存。内存该释放时没有释放,即发生了内存泄漏。
具体的引用链为:MessageQueue → Message → Handler → Activity(箭头代表持有)
(4)为什么主线程可以直接new出Handler?想要在子线程中new出Handler需要做哪些操作?
根据上面的分析,在ActivityThread的源码中我们看到main方法中已经调用了Looper.prepareMainLooper()和Looper.loop()方法,所以可以在主线程中直接new出Handler。要在子线程中new出Handler,也需要做这些操作,示例代码如下:
new Thread(new Runnable() {
Handler myHandler;
@Override
public void run() {
Looper.prepare();
myHandler = new Handler() {
public void handleMessage(Message msg) {
//处理消息
}
};
Looper.loop();
}
}).start();
(5)子线程中的Looper,在消息队列中没有消息时会怎么样?应该怎么处理?
由上面的源码分析得知当消息队列中没有消息时MessageQueue的next会阻塞,阻塞就会导致子线程无法被销毁。想要结束子线程释放资源,就要调用MessageQueue的quit方法,quit方法中最后会调用nativeWake方法来唤醒阻塞的线程,从而停掉死循环。
(6)在多个Handler往消息队列中添加数据时,如果发送时这些Handler处于不同线程,那么是如何保证安全性的?
根据源码我们发现MessageQueue的入队和出队方法中都有synchronized关键字进行加锁保护,而锁对象是this也就是当前MessageQueue。由于每个线程只有一个Looper因此也只有一个MessageQueue,所以在多线程条件下,它们发消息需要的锁是同一个,也就当一个线程拿到锁之后别的线程只能等待,这样便保证了在并发条件下的安全性。
(7)如何创建一个Message?原理是什么?
通常情况下当我们需要一个Message的时候是通过new的方式创建对象的,这样一来当消息的需求量比较大时就要创建好多个Message,造成大量创建、销毁对象的操作(内存抖动)。事实上Message为我们提供了一个obtain方法,之前已经分析过了,这个方法首先不会去new出Message,而是在缓存池中直接获取,即实现了资源的复用。那么池中的消息从哪里来呢?按常理推断,每次一个消息被处理完之后就应该被放到缓存池中,那么我们来看一下loop方法中有没有这样的操作是不是这样的:
public static void loop() {
……
for(;;){
Message msg = queue.next();
……
msg.recycleUnchecked()
}
发现for循环的最后调用了消息本身的recycleUnchecked方法,我们点开它看看:
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
根据上述代码我们发现,recycleUnchecked方法并没有将消息本身清除掉,而是将消息中携带的内容置为了null(清空),之后在同步代码块中将这条用过的消息存在缓存池的首部(头节点)。这样一来当我们调用obtain方法时就不用再new出消息,而是直接从池子中获取即可。
(8)Looper死循环为什么不会导致应用卡死(ANR)?
在之前的分析中提过,如果消息队列中没有消息,next方法就会阻塞(nativePollOnce导致主线程睡眠),那么为什么主线程无论睡眠多久都不会产生ANR问题呢?
想要说明这个问题就要先从为什么会产生ANR来说起,产生ANR的根本原因是5秒钟内没有响应按键、触屏等输入事件,或者10秒钟内广播接收器没有处理完广播。然而在Android中,这些输入事件都是以Message的形式存在的,说到这里其实问题就已经解决了。就这个问题的本质来说,主线程的睡眠和ANR根本没有半毛钱关系。产生ANR的前提是消息队列中有消息然而却没有在规定时间内被处理(消息阻塞),而主线程睡眠的前提是消息队列中没有消息,所以这两者是没有丝毫关系的,当主线程loop处理完所有消息之后就应该睡眠(该睡就得睡)。
举一个生活中的例子:老婆叫你去银行取100w,由于银行距离家较远所以需要走一段时间。刚走到银行老婆又让你去4s店提一辆奔驰,还没到4s店老婆又想喝奶茶让你去买。这样一来由于前面的事情还没办好后面的事情就被耽搁了,也就无法在老婆规定的时间内将奶茶送到她的手里(ANR产生的原因)。换一种情况,当你把老婆交代的所有事情做完,手头没有其他事情,这时候就可以睡觉了(主线程睡眠),睡了5分钟老婆突然想吃西瓜,她又会把你叫醒去买西瓜(有新的消息时主线程被唤醒)。根据这样的情景,难道可以说没有在规定时间买到奶茶的原因是你睡着了吗?很明显这样的说法是无理取闹的。