这里我们就不去讲handler的基本使用了,我们会讲一些网上没提到过的,我们会从内存共享的角度去分析handler的源码。通过这篇文章相信大家能get很多干货。那么现在,我问大家一个问题。
为什么Handler机制能实现各个线程间的内存共享呢?
带这个这个问题,直接进入主题。handler相信所有Android的开发者都会用。我们都知道在Android线程通信中涉及到几个很重要的类:Handler、Message、MessageQueue、Looper、Thread其实还包括ThreadLocal和ActivityThread。下面我给出一个Handler的通信流程。
Handler流程图
结合上面的流程图,我们知道,handler是通过sendXXX和postXXX等一系列方法发送消息的,发送的消息会按时间顺序组装到MessageQueue里面,然后Looper会执行loop方法从MessageQueue里面不断的取消息,取出来通过message的msg.target.dispatchMessage(msg)方法,返回给message指定的handler(可能是一个线程中多个不同的handler,多个线程怎么保证消息的顺序,我们后续会讲到)。
了解了handler的基本运作流程,我们再去具体分析下各个方法的调用者,我们都是知道handler是通过调用handler的sendXXX和postXXX的一些列方法发送信息的,但不管是它们其中的哪个方法,最后都会调用到MessageQueue的enqueueMessage方法去发送消息,将消息放到MessageQueue里,那么既然有发送消息,那么接收消息是怎么接收的呢?就是MessageQueue的next方法,他会去接收消息,从MessageQueue取出消息,那么next方法是谁去调用的呢?其实就是Looper.loop方法里去循环调用的。那么Looper.loop又是谁去调用的呢?其实每个非主线程都必须自己去调用Looper.loop的方法(而且在Looper.loop之前必须先调用looper的prepare方法去获取一个looper)。那么非主线程是自己去调用的,那么主线程里呢?是怎么去调用的呢?我们在使用主线程的handler发送消息的时候,我们并没有进行Looper新建这些一系列的操作啊?那到底怎么回事呢?其实在主线程在自己的mian方法里会去维护一个looper,我们Android的主线程就是ActivityThread这个类,所以我们从这个切入点,具体到源码的分析
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// 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());
AndroidKeyStoreProvider.install();
// 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.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");
}
我们进入到ActivityThread源码的main方法,我们注意到两个方法:Looper.prepareMainLooper()和 Looper.loop(),所以我们现在大致知道了,主线程其实是自己内部调用了Looper.prepare和Looper.loop。那么prepareMainLooper里面到底干了什么事情呢?我们进入到Looper的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));//把新创建的Looper和ThreadLocal关联起来,保证looper的唯一性
}
我们注意到在主线程在new一个Looper的时候把new的这个Looper通过set方法放到了ThreadLocal里,主线程在初始化Looper对象的时候,为什么要把Looper放进ThreadLocal里面么?我们都知道ThreadLocal是一个线程隔离工具类,其实这样做的目的,就是保证了多线程间的线程隔离。要具体了解ThreadLocal是怎么工作的。我们肯定得进入源码了解下ThreadLocal类,我们直接定位到ThreadLocal类的set方法里
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();//拿到当前的线程
Values values = values(currentThread);//获取这个线程的Values对象
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);//把threadLocal当作key,Looper当作value存储起来
}
第一步它是拿到当前的线程currentThread,为什么要先拿到当前线程呢?其实每个线程的内部都会有一个成员变量ThreadLocal.Values localValues;
image.png
我们再继续set里源码的分析,然后通过currentThread获取到当前线程的Values对象,然后把当前的this也就是ThreadLocal和传进来的value也就是我们新建的looper对象put进values里,那么Values对象是什么呢?我们发现Values是ThreadLocal的内部类
image.png
,除了一些int类型的成员变量,我们只发现了一个table数组(其实早些源码版本里是一个map数组,只是这个table数组实现了和map相同的功能),是数组我们会想到数组的增加和删除方法,所以我们再进入Values里面的add方法(其实put方法也是差不多的)看看,
/**
* Adds an entry during rehashing. Compared to put(), this method
* doesn't have to clean up, check for existing entries, account for
* tombstones, etc.
*/
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {//根据key获取index下标
Object k = table[index];
if (k == null) {
table[index] = key.reference;//index未知存储key
table[index + 1] = value;//index+1位置存储value
return;
}
}
}
我们发现是入参key和value的键值对,我们知道table是数组,那么为什么要用键值对map的这种方式存储呢?(上面说过了,早期版本确实是map来实现的)我们分析下这个add方法,首先他会根据key通过key.hash 获取index下标(.hash通过哈希值获取的,就是保证key的唯一性),然后下标是index的存储key,下标是index+1的存储value,所以我们综上分析,table的数组的确切数据存储方式是以threadLocal当作key,Looper当作value,并且以key1,value1,key2,value2,key3,value3..........】这种顺序存储起来的。现在,我们再回到Looper类的 prepare(boolean quitAllowed) 方法里
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和ThreadLocal关联起来,保证looper的唯一性
}
我们从上面ThreadLocal的源码分析知道ThreadLocal是通过set的方式将sThreadLocal和looper绑定起来的。我们再看到prepare里有一个 if (sThreadLocal.get() != null) {这样的判断,它的意思是通过ThreadLocal去取对应的looper,如果已经存在了,就会抛出"Only one Looper may be created per thread"异常,所以它保证了每个ThreadLocal的values的table数组里存储的looper是唯一的,也就是说ThreadLocal和Looper是一一对应的,不会重复的,又因为每个Thread都会有一个ThreadLocal.Values localValues的成员变量,所以
结论一:不管是主线程还是非主线程,每一个Thread对应了一个唯一的looper。(当然一个线程可以创建多个Handler)。
分析完主线程里的prepareMainLooper方法,我们再进入主线程里的另外一个方法Looper.loop()里去看看,所以我们进入Looper类的looper方法去分析:
public static void loop() {
final Looper me = myLooper();//保证Looper的唯一性
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue v = me.mQueue;//保证MessageQueue的唯一性
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
//死循环里从MessageQuene里取消息,没有消息就会一直等待
//如果没有要执行的消息,就会一直在等待,所以主线程永远不会退出,有消息就执行消息,没消息就一直等待
//这就是主线程永远不需要创建handle,但可以一直执行,而且looper一直在工作的原因
//主线程不是在工作,就是在等待
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);//通过调用target(实际是handle实例)的dispatchMessage传回给handle
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();//msg会去回收msg
}
}
我们看到final Looper me = myLooper();这一句,一开始先是去拿到一个looper对象,我们进入myLooper()看看是怎么拿到的?
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//从ThreadLocal里拿到Looper对象
}
它就是从sThreadLocal里去拿到的,就是保证了looper对象的唯一性,只要你这个线程new了一次looper,以后不管你在这个线程的哪里再次用到looper,都会去sThreadLocal去拿。
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
如果你的looper是空,会报异常,提示你"No Looper; Looper.prepare() wasn't called on this thread.",小伙伴么,这个异常我们在刚刚开始学习多线程通信的时候是不是常常遇到啊?我们想实现两个子线程的通信,是不是要在其中一个子线程里调用looper.loop方法啊?我们在调用looper.loop之前是不是要先调用Looper.prepare()?所以你看这里,如果你在looper.loop之前不调用Looper.prepare()是会报异常的,我们继续往下看,然后再通过looper对象,拿到MessageQueue对象,我们先去看看这个MessageQueue对象怎么拿到的?进入到looper的构造方法看看
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//保证了MessageQueue的唯一性
mThread = Thread.currentThread();
}
MessageQueue就是在Looper的构造方法里创建的,因为looper我们知道在一个线程里是唯一的,所以这样创建MessageQueue的好处就是保证了MessageQueue的唯一性。我们接着看,我们发现取消息是在一个死循环里去取的?那么为什么是一个死循环呢?直接死循循环就是为了保持当前应用执行不退出。
image.png
我们看到ActivityThread执行到最后就会抛出异常,实际上因为looper.loop的死循环,主线程是不会执行到这一步的。我们接下去看,我们看到 死循环里有那么一句代码Message msg = queue.next(); // might block,就是说这句代码kennel会阻塞,具体怎么会阻塞呢?我们后面再去分析messagequeue的收发消息机制(enqueueMessage和next),这个问题就能解答了,现在先把loop的流程看完。我们直接看到msg.target.dispatchMessage(msg);这一句代码,这句代码的意思,就是会把接收到的消息,通过msg的target的dispatchMessage方法,将消息返回给上层的handler,其实这个target对象就是msg的一个成员变量,它是handler类型的,就是为了区分不同消息的来源。最后在看到loop方法里的最后一句代码 msg.recycleUnchecked();//msg会去回收msg,我们现在先想一个问题,在 handler流程里,有那么多msg对象,它是怎么管理的呢?是每次都去new一个msg对象么?我们进入Messenger类的recycleUnchecked方法
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {//
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
//享元模式(并不是销毁msg,而是把msg里的各种成员变量置为null,下次有新的消息的时候,直接复用
//,而不是再去new msg,减少了内存碎片的出现,减少了内存抖动,提高了程序的性能
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++;
}
}
}
它这里其实就是用到了一个java的设计模式:享元模式 。也就是每次并不是销毁msg,而是把msg里的各种成员变量置为null,下次有新的消息的时候,直接复用,而不是再去new新的msg,我们知道new的过程就是在jvm里开辟一块内存区域的过程,如果每次new一下,是不是会出现很多内存碎片啊?但有人会说,其实虚拟机会去gc回收啊?小伙伴们想,等你要到gc回收的时候其实已经是内存不够用的时候了,我们直接不让内存不够用不是更好么,更能提升app的性能么,所以,享元模式其实就是为了减少了内存碎片的出现,减少了内存抖动,提高了程序的性能。好了loop源码讲完了,为什么了解loop是怎么休眠的,怎么被唤醒的,Message msg = queue.next();这句代码为什么会被阻塞,我们现在直接进入messagequeue的next和enqueueMessage的源码分析
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();
}
//native函数没进入等待的方法nextPollTimeoutMillis=-1会一直等待,
//nextPollTimeoutMillis>0会等待nextPollTimeoutMillis时间
//nextPollTimeoutMillis = 0;不会等待
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) {
//如果当前的时间now还没达到消息执行的时间when,就开始等待
if (now < msg.when) {//msg.when是当前时间加上消息的延迟时间,在handle发送消息的时候会带过来(SystemClock.uptimeMillis() + delayMillis)
// Next message is not ready. Set a timeout to wake up when it is ready.
//等待的时间就是msg.when - now,也就是再经过这些时间再执行这个消息
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 {//msg==null没有消息的时候nextPollTimeoutMillis为-1
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {//消息为空走这个if
//将 mIdleHandlers.size()赋值给pendingIdleHandlerCount,因为大多数情况下 mIdleHandlers.size()是为0的,所以
//pendingIdleHandlerCount大多数情况下等于0,也就是大多数情况下会走下面pendingIdleHandlerCount <= 0这个if,
//所以主线程大多数是不走这个死循环的会直接continue,直接continue也就是extPollTimeoutMillis为-1
//extPollTimeoutMillis为-1传入native等待函数的时候native等待函数会一直等待。
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {//消息不为空走这个if,
// No idle handlers to run. Loop and wait some more.
mBlocked = true;//阻塞标志置为true
continue;//continue就会退出当前的循环继续走循环的下一个,所以循环末尾的 nextPollTimeoutMillis = 0;不会走,所以调用前面的native休眠的方法,进入等待
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 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;
}
}
//生产者:消息是按时间排序的,越早进来的消息(加上消息延迟时间),越排在队伍的前面
boolean enqueueMessage(Message msg, long when) {
if (msg.target == 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;
if (p == null || when == 0 || when < p.when) {//当前要执行的消息(队头消息)为空,说明消息已经被取走了
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;//把新消息赋值给队头消息
needWake = mBlocked;//如果线程是阻塞的,就把needWake置为true,在下面唤醒线程
} 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);//native函数唤醒主线程,让主线程工作,不再等待
}
}
return true;
}
我们看到,不管是next方法和enqueueMessage方法都用了synchronized修饰,其实就是为了保证线程安全。我们想在线程间通信时,消息的next和enqueueMessage是不是可能被多个线程调用啊?例如现在thread1调用主线程的handler发送消息,同时thread2也调用这个handler发送消息,那么怎么保证MessageQueue的顺序呢?怎么保证保消息收发的顺序呢?所以必须加上synchronized修饰。回过头来,那么整个MessageQueue是什么设计模式呢?其实enqueueMessage往MessageQueue里添加消息是一个生产者,next往MessageQueue里拿消息是一个消费者,MessageQueue就是一个仓库,而这个仓库就是由message构成的队列。
image.png
为了保证这个仓库里的所有的消息不变乱,当我往这个仓库里添加消息的时候,这个生产者可能是多个线程的,所以加入了synchronized修饰添加消息的方法enqueueMessage,同时从这个仓库取出消息的时候,为了保证一个消息只被一个线程取出,所以加入了synchronized修饰取出方法next。大方向讲完了,我们现在直接进入next方法的分析阻塞和唤醒机制。我们看到 nativePollOnce(ptr, nextPollTimeoutMillis);这个方法,这个就是线程挂起的native方法,我们发现里面有一个nextPollTimeoutMillis入参 ,其实这个方法的原理是这样的,如果nextPollTimeoutMillis=-1会一直等待;如果nextPollTimeoutMillis>0会等待nextPollTimeoutMillis时间;如果nextPollTimeoutMillis = 0;不会等待,我们再往下看,那是怎么实现线程挂起的呢?
我们先看到 int pendingIdleHandlerCount = -1; ; int nextPollTimeoutMillis = 0;这两个变量一开始pendingIdleHandlerCount为-1;nextPollTimeoutMillis为0;我们看到if (msg != null) {也就是msg不为空,来消息了,它还有一个if (now < msg.when)这个判断。msg.when是什么呢?其实msg.when是当前时间加上消息的延迟时间,在handle发送消息的时候会带过来(SystemClock.uptimeMillis() + delayMillis),这个判断的意思就是如果当前的时间now还没达到消息执行的时间when,然后他把nextPollTimeoutMillis赋值为msg.when - now,也就是消息的延迟时间,else不用说,就是执行这个消息
image.png
我们在看到下面的这段代码,我们的消息不为空,并且还没到消息的执行时间时走下面 if (pendingIdleHandlerCount <= 0) {这个判断,它是把阻塞标志mBlocked置为true(mBlocked这个标志我们在enqueueMessage方法里会讲到,大家留意下),然后再continue,就会退出当前的循环继续走循环的下一个,所以循环末尾的 nextPollTimeoutMillis = 0;不会走,所以调用前面的native休眠的方法,进入线程挂起。我们再分析下上面这个判断, if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {看这个意思就是mMessages为空的时候走这个判断,然后把 mIdleHandlers.size()赋值给pendingIdleHandlerCount;因为大多数情况下 mIdleHandlers.size()是为0的,所以pendingIdleHandlerCount大多数情况下等于0,也就是大多数情况下会走下面pendingIdleHandlerCount <= 0这个if,
因为一开始判断我们知道msg为空的时候nextPollTimeoutMillis=-1
所以continue跳出循环走到native等待函数的时候native等待函数会一直等待。
所以经过上面的分析我们得出
结论二:、没有消息的时候。2、消息还没达到发送的时间的时候,线程都会被挂起
分析完next方法,我们再进入enqueueMessage方法看看
if (p == null || when == 0 || when < p.when) {//当前要执行的消息(队头消息)为空,说明消息已经被取走了
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;//把新消息赋值给队头消息
needWake = mBlocked;//如果线程是阻塞的,就把needWake置为true,在下面唤醒线程
} 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);//native函数唤醒主线程,让主线程工作,不再等待
}
我们直接看到 if (p == null || when == 0 || when < p.when) 这个判断的意思是:当前要执行的消息(队头消息)为空,说明消息已经被取走了,然后会把把新消息赋值给队头消息,然后把阻塞标志mBlocked的值赋给needWake,如果阻塞表示是ture,就会调用nativeWake唤醒线程,让线程不再等待。大家还记得么?在next取出方法的时候,如果没消息或者消息还没到达发送时间我们最后是把mBlocked置为true的,这里加入消息的方法enqueueMessage刚刚好利用了这个标志,如果当前线程的消息又来了,就把线程唤醒。这个配合就实现了整个handler的消息收发机制,也解答了loop里为什么Message msg = queue.next(); // might block这句话可能会阻塞的原因,这个实现机制是不是特别精辟。
到此,整个handler消息的收发机制都讲完了,相信大家都能回答为什么handler机制能实现线程间的内存共享这个问题了。
总结:其实归结到一点就是MessageQueue,这个MessageQueue里每一个节点message是不是一个独立的内存,不管你在主线程还是子线程里创建的message,都是一个独立的线程,它们之间的通信,就是在于共享MessageQueue这片空间,为了实现线程间的隔离,使用了ThreadLocal;为了让MessageQueue能够被多个线程同时调度不会冲突,使用了synchronized线程锁;为了使MessageQueue中的message能够被最大程度的复用,使用了享元设计模式;然后整个MessageQueue的收发消息又是使用了生产者消费者模式。
最后我们讲一些亮点代码,也就是面试中经常会问到的问题。
问题一:handler的dispatchMessage方法
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们在使用handler的时候,是不是有以下几种方式1、会让msg自己去实现一个callback.2、我们会给handler传一个callback。3、直接继承handler,实现
handleMessage方法。其实这也是面向对象的一个封装的思想,把处理消息的方法封装到了一个dispatchMessage方法里,其实事件的分发机制里,也使用了这种思想。
问题二:Looper什么时候退出(其实handler内存泄露有很多要点要讲,其实原因就是该回收的变量的生命周期比当前的生命周期长,我这里就不展开了,网上有很多文章大家都可以去看,其实主要几点避免内存泄露的方法,就是1、使用静态内部类定义Handler 2、对Activity的引用使用弱引用3、在销毁方法里例如Activity的onDestroy及时去回收)。
在子线程中Looper经常会内存泄露,就是因为Looper没有释放。所以我们需要释放Looper。Looper结束其实有两种方法,
public void quit() {
mQueue.quit(false); //消息移除
}
public void quitSafely() {
mQueue.quit(true); //安全地消息移除
}
void quit(boolean safe) {
// 主线程的MessageQueue.quit()行为会抛出异常,
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);
}
}
问题三:Thread、Handler、Looper、MessageQueue之间的对应关系。
一个Thread可以创建多个Handler,但只能创建一个Looper,一个MessageQueue。Handler跟Looper之间没有对应关系;多个Thread可以同时使用某个线程中的Handler来发送消息。
网友评论