Handler、MessageQueue、Looper原理分析详解手机开发

网上有超级多的讲述Handler原理的文章,可是还得自己重新整理一遍,这样理解才能更加深刻,毕竟binder和Handler可以算是Android的基石。

按照套路,先抛出自己的问题:

  1. 子线程怎么创建使用Handler?
  2. 主线程的Looper一直循环怎么没有导致ANR?
  3. 主线程的Looper在没有消息的时候会被阻塞,那么主线程不会被一直阻塞吗?
  4. HandlerThread是什么?以及内部是怎样的?

先不看源码分析,直接看第一个问题:

1. 子线程怎么创建使用Handler?

Looper looper = null; 
 
Thread subThread = new Thread() {
    
	@override 
	public void run() {
    
		Looper.prepare(); 
		looper = Looper.myLooper(); 
		Looper.loop();			 
	} 
} 
 
subThread.start(); 
 
Handler handler = new Handler(looper) {
    
	@override 
    public void handlerMessage(Message msg) {
    
        // 这部分处理运行subThread线程中 
    } 
} 

以上是创建在子线程的Handler的一种方法,一开始也是感到很疑惑,什么Looper.prapare()、Looper.loop()?一步一步慢慢来。

ThreadLocal

在理解Looper之前,先看下ThreadLocal,ThreadLocal不止使用起来很方便,其实原理也不难。

static ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>(); 
 
// 在线程1 
mThreadLocal.set(1); 
int value = mThreadLocal.get(); //value: 1 
 
// 在线程2 
mThreadLocal.set(2); 
int value = mThreadLocal.get(); //value: 2 
 
// 在线程3 
int value = mThreadLocal.get(); //value: null 

可见同一个ThreadLocal变量在每个线程都是独立的值,相互之间并不影响;在线程1对mThreadLocal做的操作,在线程2并不可见。

ThreadLocal原理

ThreadLocal的原理用一张图就可以概括:

在这里插入图片描述

每个Thread内部有一个表ThreadLocalMap(类似一个简单版的HashMap),内部存储的键值对<ThreadLocal, object>

ThreadLocal提供set()get()方法;其中set方法就是在当前线程的ThreadLocalMap插入一个<ThreadLocal, object>,而get方法就是在当前线程的ThreadLocalMap,通过ThreadLocal本身为key,取出值object。

这样一来,同一个ThreadLocal在不同的线程存放不同的value,反过来,拿着ThreadLocal就能从不同的线程取出不同的value,因为ThreadLocal可以充当key。

下面从源码角度验证以上的理论,简要地分析
在这里插入图片描述

public class Thread implement Runnable{
    
    ThreadLocal.ThreadLocalMap threadLocals = null; 
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 
} 

类Thread含有两个变量threadLocals、inhreitableThreadLocals,两个都是ThreadLocalMap,区别在于后者inhreitableThreadLocals在初始化的时候会将父线程的ThreadLocals的值 给复制到新线程中的ThreadLocals。

简单看一下ThreadLocal的get()set()remove()方法

get()
public T get() {
    
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t);// 获取当前线程中的ThreadLocalMap 
    if (map != null) {
    
        ThreadLocalMap.Entry e = map.getEntry(this);// 以本身ThreadLocal为key 从map取出value 
        if (e != null) {
    
            @SuppressWarnings("unchecked") 
            T result = (T)e.value; 
            return result; 
        } 
    } 
    return setInitialValue();// map为null,初始化 
} 

get() 流程:

  1. 获取当前线程中的ThreadLocalMap
  2. 以ThreadLocal本身为key,从ThreadLocalMap对应的value
set(T)
public void set(T value) {
    
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t); 
    if (map != null) 
        map.set(this, value); 
    else 
        createMap(t, value); 
} 

set() 流程:

  1. 获取当前线程中的ThreadLocalMap
  2. 以ThreadLocal本身为key,从ThreadLocalMap删除Entry

ThreadLocalMap解决Hash冲突的方式就是寻找下一个相邻的位置;

ThreadLocalMap采用的是WeakReference为key,所以当ThreadLocal被回收,key就为null,而value却是强引用,这样是不是会造成内存泄漏呢?

ThreadLocalMap是这样处理的,在get()set()remove()等方法都会清除掉key为null的结点。

ThreadLocal可能会造成内存泄漏的原因:

  1. 在设置set()以后没有remove();
  2. 在设置set()之后一段时间,ThreadLocal虽然已经被回收了,但是没有再get()或者remove()等方法,导致没有机会清除value。

Message

Message 表示消息,Handler每次发送或者处理的一个Message。

平时,我们都是这样获取一个Message:

Message msg = Message.obtain(); 

先看一下Message的结构

public class Message {
    
    long when; // 表示延迟时间 
    Bundle data;  
    Handler target; // 发送和最后处理的Handler 
    Runnable callback; 
    ... 
    Message next; 
    private static Message sPool; 
    private static int sPoolSize = 0; 
    private static final int MAX_POOL_SIZE = 50; 
} 

Message内部有一个链表sPool,通过next串连起来,表示Message池,最大为50个。

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(); 
} 

obtain()就是在sPool池中取一个Message,否则就new一个Message。

对应的看一下recycle()回收方法,负责将Message存入sPool,留取下次使用

public void recycle() {
    
    ... 
    recycleUnchecked(); 
} 
 
void recycleUnchecked() {
    
    ...// 清除Message资源、标记 
    synchronized (sPoolSync) {
    
        if (sPoolSize < MAX_POOL_SIZE) {
    
            next = sPool; 
            sPool = this; 
            sPoolSize++; 
        } 
    } 
} 

MessageQueue

大致看完Message,自然就引出了MessageQueue;顾名思义,Message的队列,负责加入Message,对外提供Message。

重要的变量:
Message mMessages; Message链表,负责储存Message

主要看两个方法:

boolean enqueueMessage(Message msg, long when); 加入Message
Message next(); 获取下一个Message

enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
 
if (msg.target == null) {
 // message的handler不能为空 
throw new IllegalArgumentException("Message must have a target."); 
} 
if (msg.isInUse()) {
 // message被使用了 
throw new IllegalStateException(msg + " This message is already in use."); 
} 
synchronized (this) {
 
if (mQuitting) {
 // MessageQueue已经被调用quit()退出了 
IllegalStateException e = new IllegalStateException( 
msg.target + " sending message to a Handler on a dead thread"); 
msg.recycle(); 
return false; 
} 
msg.markInUse(); // 设置msg被使用了 
msg.when = when; // 设置msg延迟时间 
Message p = mMessages; // mMessages为Message链表头结点 
boolean needWake; 
if (p == null || when == 0 || when < p.when) {
 
// 条件:Message链表为空,或者延迟时间为0,就是说需要马上执行, 或者延迟时间比头结点的Message的延迟时间短 
// 结果:将msg设置为头结点 
msg.next = p; 
mMessages = msg; 
needWake = mBlocked; 
} else {
  
needWake = mBlocked && p.target == null && msg.isAsynchronous(); 
Message prev; 
// 以下就是找出插入msg到mMessage的位置 
// 根据when延迟时间来决定,延迟短的排在前面,延迟长的排在后面 
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; 
} 

enqueueMessage() 流程:

  1. 插入Message到mMessages队列中,插入位置根据延迟时间决定,延迟短的排在前面,延迟长的排在后面
  2. mMessages插入了消息,nativeWake()是native方法,会唤醒MessageQueue的next()继续执行,这部分需要跟后面的next()结合。

在这里插入图片描述

next()
Message next() {
 
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(); 
} 
nativePollOnce(ptr, nextPollTimeoutMillis);// 进入阻塞,休眠nextPollTimeoutMillis时间 
synchronized (this) {
 
// 获取当前时间 
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. 
// 从mMessages往后找,找到一个合法的Message 
do {
 
prevMsg = msg; 
msg = msg.next; 
} while (msg != null && !msg.isAsynchronous());// 这里不考虑异步消息 
} 
if (msg != null) {
 
if (now < msg.when) {
 // 如果msg的时间还没到 
// nextPollTimeoutMillis 是需要休眠多长时间	 
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); 
} else {
 
// Got a message. 
mBlocked = false; 
// 从mMessages删去msg 
if (prevMsg != null) {
 
prevMsg.next = msg.next; 
} else {
 
mMessages = msg.next; 
} 
msg.next = null; 
msg.markInUse(); 
return msg;// 返回 
} 
} else {
 
nextPollTimeoutMillis = -1; 
} 
if (mQuitting) {
 // 如果MessageQueue退出了 
dispose(); // 收尾 
return null;// 返回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; 
} 
if (mPendingIdleHandlers == null) {
 
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; 
} 
// IdleHandler 表示 MessageQueue空闲时候的需要进行通知下IdleHandler接口 
// mIdleHandlers 是 IdleHandler接口的集合,是一个观察者模式 
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); 
} 
} 
} 
pendingIdleHandlerCount = 0; 
nextPollTimeoutMillis = 0; 
} 
} 

next() 流程:

  1. next()方法是一个死循环,从mMessages队首中获取一个Message,如果Message到时了,则直接返回出去;
  2. 如果队首Message还没到时了,计算一个等待时间(nextPollTimeoutMillis)并调用nativePollOnce()进入阻塞状态;
  3. 如果当前其他线程enqueueMessage()插入消息到队列头部,会调用了nativeWake(),唤醒执行当前线程继续执行next(),获取消息。

上面没有考虑同步屏障的问题,默认Message都是同步消息,但是Message可以调用setAsynchronous(true)设置成异步消息。那么同步消息和异步消息有什么不同呢?

MessageQueue提供了postSyncBarrier()设置同步屏障和removeSyncBarrier()移除同步屏障,其实设置同步屏障就是插入一个target为null的Message到消息队列中,如果碰到同步屏障,那么同步屏障之后的同步消息会被阻拦,异步消息不会收到影响。这部分只做了简单的了解。

    public static interface IdleHandler {
 
boolean queueIdle(); 
} 

IdleHandler 表示 MessageQueue空闲时候的会回调这个接口,如果返回false,那么从mIdleHandlers移除它,返回true就会在下次空闲的时候继续回调。更多的用法参考:云图网

Looper

Looper 主要负责派发Message给Handler处理。

Looper内部有一个MessageQueue,负责存储待处理的Message。

看下怎么为当前线程生成一个Looper

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()生成一个Looper
loop() 不断轮询,从MessageQueue获取消息并派发消息

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,通过sThreadLocal保存,上文也介绍了ThreadLocal的特性,这样能保证每个线程都有一个自己的Looper,不会相互影响。

public static @Nullable Looper myLooper() {
 
return sThreadLocal.get(); 
} 

myLooper从sThreadLocal取出,返回当前线程的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); // 分发给Handler处理Message 
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; 
}  
... 
msg.recycleUnchecked();// 回收Message 
} 
} 

loop()流程:

  1. loop() 是一个死循环,不断的从MessageQueue.next()获取消息Message;
  2. 如果MessageQueue没有消息next()会将当前线程阻塞,直至有消息;
  3. 如果MessageQueue的next()返回null,则说明要退出loop()循环;
  4. 获取得到msg,调用msg.target.dispatchMessage分发,msg.target就是发送消息的Handler,也就是最后会分派给Handler处理消息;
  5. 处理完成,msg.recycleUnchecked()将Message回收,可以看上文的关于Message介绍。

Looper的退出有两个方法:quit()quitSafely()

// Looper.java 
public void quit() {
 
mQueue.quit(false); 
} 
public void quitSafely() {
 
mQueue.quit(true); 
} 
// MessageQueue.java 
void quit(boolean safe) {
 
if (!mQuitAllowed) {
 
throw new IllegalStateException("Main thread not allowed to quit."); 
} 
synchronized (this) {
 
if (mQuitting) {
 
return; 
} 
mQuitting = true;// 设置退出标志,让MessageQueue不再接收消息 
if (safe) {
 // 安全退出 
removeAllFutureMessagesLocked(); 
} else {
 
removeAllMessagesLocked(); 
} 
// 唤醒MessageQueue 
nativeWake(mPtr); 
} 
} 

removeAllFutureMessagesLocked() 会清除MessageQueue中所有延迟的消息
removeAllMessagesLocked() 会清除MessageQueue中的消息

看Handler如何结合Looper做到切换线程处理消息。

Handler

如何构造一个Handler :

public Handler() {
 
this(null, false); 
} 
public Handler(Callback callback, boolean async) {
 
... 
mLooper = Looper.myLooper();// 获取当前线程的Looper 
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和MessageQueue,如果Looper不存在会抛出异常。

这也是子线程为什么构造Handler会出错的原因。

public Handler(Looper looper) { 
this(looper, null, false); 
} 

Handler也支持指定一个Looper。

Handler发送消息
public final boolean post(Runnable r) 
{
 
return  sendMessageDelayed(getPostMessage(r), 0); 
} 
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
 
MessageQueue queue = mQueue; 
if (queue == null) {
 
RuntimeException e = new RuntimeException( 
this + " sendMessageAtTime() called with no mQueue"); 
return false; 
} 
return enqueueMessage(queue, msg, uptimeMillis); 
} 
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
 
msg.target = this; 
if (mAsynchronous) {
 
msg.setAsynchronous(true); 
} 
return queue.enqueueMessage(msg, uptimeMillis); 
} 

Handler发送消息,最后还是走到了MessageQueue.enqueueMessage(),往MessageQueue插入一个消息。

Handler处理消息

Looper.loop()中有一句分发处理消息:

// Looper.loop() 
msg.target.dispatchMessage(msg); 

其中target就是Handler

// 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 是Runnable,通过post(Runnable)转换成Message;
mCallback 是通过Handler构造函数传入的,一般是没有设置,默认为null;
最后走入了我们最熟悉的handleMessage(),也是经常覆盖重写的处理Message的方法。

总结

  1. 在其他线程中,通过Handler将一个消息Message插入到消息队列MessageQueue;
    2/ 在本线程中,Looper从MessageQueue获取一个Message,这个Message可能来源于其他线程;
  2. 在线程中,最后Looper派发给Handler处理。

以上就做到了切换线程的操作。

在这里插入图片描述

问题解答:

  1. 子线程怎么创建使用Handler?

    从上面的整个介绍可以看出,一个线程是否能够使用Handler,是看它有木有处于Looper的循环中,只有在Looper的循环中才可以处理Message,Handler更只是一个发送数据和处理数据挂件。

Looper looper = null; 
Thread subThread = new Thread() {
 
@override 
public void run() {
 
Looper.prepare();// 生成线程的Looper 
looper = Looper.myLooper(); 
Looper.loop();// 让Looper运作起来 
} 
} 
subThread.start(); 
// 发送消息和在handleMessage处理消息 
Handler handler = new Handler(looper) {
 
@override 
public void handlerMessage(Message msg) {
 
// 这部分处理运行subThread线程中 
} 
} 
  1. 主线程的Looper一直循环怎么没有导致ANR?

    Android Activity中,各种生命周期都运作在Looper,包括onCreate、onResume等

    在Activity的入口,类ActivityThread可以看到

	// ActivityThread 
public static void main(String[] args) {
 
... 
Looper.prepareMainLooper(); 
ActivityThread thread = new ActivityThread(); 
... 
if (sMainThreadHandler == null) {
 
sMainThreadHandler = thread.getHandler(); 
} 
... 
Looper.loop(); 
} 
AcitivtyThread的main方法是Activity启动的入口,可以看到`Looper.prepareMainLooper()`和`Looper.loop()`,而sMainThreadHandler是Handler。 
所以反过来说,之所以会发送ANR,是因为Looper在循环处理消息的过程中,某一个消息处理时间太长了,导致发生ANR,例如在主线程中做网络操作。 
  1. 主线程的Looper在没有消息的时候会被阻塞,那么主线程不会被一直阻塞吗?

    确实,从上面的代码可以看出,当Looper在next()时如果没有消息处理,会发生阻塞,但是一旦有消息MessageQueue.enqueueMessage()也会唤醒,使得Looper继续处理消息

  2. HandlerThread是什么?以及内部是怎样的?

    HandlerThread是一个内部具有Looper的线程Thread,直接看下代码:

public class HandlerThread extends Thread {
 
Looper mLooper; 
private @Nullable Handler mHandler; 
public void run() {
 
mTid = Process.myTid(); 
Looper.prepare(); 
synchronized (this) {
 
mLooper = Looper.myLooper(); 
notifyAll(); 
} 
Process.setThreadPriority(mPriority); 
onLooperPrepared(); 
Looper.loop(); 
mTid = -1; 
} 
public Handler getThreadHandler() {
 
if (mHandler == null) {
 
mHandler = new Handler(getLooper()); 
} 
return mHandler; 
} 
} 

可见,HandlerThread就是一个问题1的答案,在run()进行loop()开启消息循环,外部可以通过getThreadHandler()获取Handler,发送消息,最后在HandlerThread所在的子线程中处理消息。

在Android中,一切皆为消息。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6270.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论