handler 我们都知道,是android多线程的一种通信机制,很多人也知道怎么用它,并且也大致知道它的工作流程,但是如果有人问到子线程是怎么将消息发到主线程的?,消息循环会不会耗资源?为什么线程间的通信不会干扰?怎么保证每个线程的Looper与MessageQueue是唯一的
接下来我会从源码的角度来回答上面的问题
消息发送
public class MainActivity extends AppCompatActivity {
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
private void startThread(){
new Thread(new Runnable() {
@Override
public void run() {
Message message = mHandler.obtainMessage();
mHandler.sendMessage(message);
}
}).start();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startThread();
}
}
如上所示在ui线程创建了一个handler实例对象,然后我在子线程拿到这个handler对象之后就可以往主线程发消息了,这看起来很简单;但这个时候可能有人会问了,那子线程的消息是怎么发到主线程去的呢?首先我们来看下ui线程handler是怎么创建的:
public Handler(@Nullable Callback callback, boolean async) {
//代码省略...
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
如上所示,handler在创建的时候调用来一个关键方法Looper.myLooper()
:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
我们看到这个方法返回来一个Looper对象,而这个Looper对象其实就是ui线程的Looper对象,为什么这么说呢?我们可以看到这个Looper对象是从sThreadLocal
这个变量里面获取的,而sThreadLocal
其实就是ThreadLocal:
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
我们知道ThreadLocal在多线程的时候是做线程隔离用的,它保证来本线程的变量不会被其他线程所改变,而且我们可以看到它是一个static final
的变量,也就是说在整个jvm中,这个变量是唯一的,任何线程都是可以访问的,那问题来了,你怎么知道这个Looper一定就是ui线程的Looper呢?我们来看一下源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
从源码我们看到,它是先拿到当前线程,因为我们的handler就是在ui线程创建的,所这个当前线程其实就是ui线程,然后它从ui线程中拿到ThreadLocalMap
, 然后从这个map中得到了Lopper对象,既然是从ui线程中得到的Looper对象,那这个Looper对象当然是ui线程的;那这个Looper对象是什么时候创建的呢?
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()方法中,它将Looper实例化之后放到了ThreadLocal里面,还需要注意的是if (sThreadLocal.get() != null)
这段代码,它会先获取looper,如果sThreadLocal里面有,那就直接抛异常,这就保证了线程中Looper的唯一性;那这个prepare方法是啥时候调用的呢?其实是在ActivityThread的main方法中调用的,大家都知道ActivityThread的main()是android app的入口方法,在我们启动app的时候它会首先走ActivityThread:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//代码省略...
Looper.prepareMainLooper();
//代码省略...
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");
}
可以看到它调用了prepareMainLooper方法:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
最终在prepareMainLooper里面调用了prepare方法,而Looper的初始化,前面已经说了,就是在prepare方法里面初始化的,并且将它放到了sThreadLocal
里面;这样我们就搞清楚了,为什么子线程可以往主线程发消息了,因为子线程有主线程创建的handler对象,而handler对象在创建的时候通过sThreadLocal拿到了主线程的Looper对象,所以在发消息的时候,它是先得到了Looper中的MessageQueue对象,然后将消息发到了MessageQueue里面,而MessageQueue的创建又是在Looper初始化的时候创建的,由于Looper是UI线程的,那Looper中的MessageQueue自然也是ui线程的
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
接下来我们就看下sendMessage方法的调用过程:
可以看到它最终会调用到enqueueMessage这个方法,而enqueueMessage这个方法是属于MessageQueue的
boolean (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 {
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
可以看到,在这个方法中使用了synchronized
关键字,因为MessageQueue的这块区域是线程共享的,意思就是只要能拿到MessageQueue对象,任何线程都是可以访问这个方法的,所以这就牵扯到线程安全问题,为了让消息的加入有个先来后到,所以在将消息加入到消息队列的时候用synchronized
做了线程安全访问,这样就保证了消息队列中的消息是有序的;好了,当子线程的消息加入到主线程的消息队列里面之后,消息循环Looper就是会从消息队列里面去消息,这时候有人会问了,Looper是怎么知道消息队列里面是有消息的呢?
我们看一下enqueueMessage
方法,其中有这么一段代码:
if (needWake) {
nativeWake(mPtr);
}
这个nativeWake方法其实就是线程唤醒方法,也就是说如果需要唤醒Looper中的操作的话它会执行nativeWake方法来做唤醒操作,那这个needWake
什么时候才是true呢?
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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
}
可以看到needWake
是由mBlocked
决定的,而这个mBlocked就是是否需要阻塞的boolean变量,那这个mBlocked
什么时候变成true了呢?
我们看下looper的loop()方法:
public static void loop() {
final Looper me = myLooper();
//代码省略...
final MessageQueue queue = me.mQueue;
//代码省略...
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next(); // might block
msg.recycleUnchecked();
}
}
可以看到,它最终调的是MessageQueue的next方法:
Message next() {
//代码省略...
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
}
//代码省略...
}
可以看到,当pendingIdleHandlerCount <= 0
的时候,mBlocked
才是true, 也就是说,如果队列是空的话它就是true, 这里有个方法很重要就是线程等待的方法nativePollOnce(ptr, nextPollTimeoutMillis);
这个方法和前面说的nativeWake(mPtr);
方法对应;也就是说如果队列是空的话,那就不用继续循环,直接调用nativePollOnce方法等待,这个nativePollOnce就是线程等待的方法,这个时候for循环就不会走了,也就节省了一部分循环开销,因为线程等待的时候会让出cpu的资源,所以看样子是一个死循环,但是在空闲的时候,线程是等待的,基本上是不消耗资源的;所以在上面将消息加入到队列之后会判断需不需要唤醒looper中等待的操纵,如果需要的话就调用nativeWake
方法唤醒,假如说队列中是有消息的并且不是等待状态的话就不需要唤醒,这个时候looper就会继续循环从消息队列里面取消息,知道队列里面没消息,然后调用nativePollOnce
方法进入等待状态来等待新消息加入之后重新唤醒!
消息处理
在loop里面获取到消息之后就会调用dispatchMessage来做消息分发:
public static void loop() {
final Looper me = myLooper();
for (;;) {
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
msg的target变量其实就是handler,我们在调用handler的obtainMessage
方法的时候,就将我们创建的handler的对象传给了msg的target变量:
public final Message obtainMessage()
{
return Message.obtain(this);
}
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
接下来看一下handler的消息分发:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这下是不是熟悉了,它先判断msg的callback是不是空的,然后判断handler的callback是不是空的,如果都是空的话,就调用handleMessage,这样的话消息就回调到我们的handleMessage这里了;
另外在消息分发处理之后,在loop方法里面还调用了msg.recycleUnchecked();
方法对消息进行回收,看样子是回收其实是将消息里面的成员置空,然后放到一个缓存池里面:
void recycleUnchecked() {
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++;
}
}
}
这样有什么作用呢,还记得我们通过handler调用obtainMessage来获取消息的方法吗,这个方法取的就是这个缓存池里面的消息:
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();
}
如果缓存池里面没有消息的话,就创建一个消息出来,这样的话就可以减少对象的频繁创建,减少内存碎片优化内存!
到此,消息从子线程发送到主线程处理的流程就说完了!