Android Handler消息机制核心代码

2022-04-23  本文已影响0人  小城哇哇

前言

该文主要是分析Handler消息机制的关键源码,阅读前需要对handler有一些基本的认识。这里先简要回顾一下:

基本组成

完整的消息处理机制包含四个要素:

  1. Message(消息):信息的载体
  2. MessageQueue(消息队列):用来存储消息的队列
  3. Looper(消息循环):负责检查消息队列中是否有消息,并负责取出消息
  4. Handler(发送和处理消息):把消息加入消息队列中,并负责分发和处理消息

基本使用方法

Handler的简单用法如下:

Handler handler \= new Handler(){  
@Override  
public void handleMessage(@NonNull Message msg) {                       
super.handleMessage(msg);  
    }  
};  
Message message \= new Message();  
handler.sendMessage(message);  

注意在非主线程中的要调用Looper.prepare()Looper.loop()方法

工作流程

其工作流程如下图所示:

从发送消息到接收消息的流程概括如下:

  1. 发送消息
  2. 消息进入消息队列
  3. 从消息队列里取出消息
  4. 消息的处理

下面就一折四个步骤分析一下相关源码:

发送消息

handle有两类发送消息的方法,它们在本质上并没有什么区别

这里不分析具体的方法特性,它们最终都是通过调用sendMessageAtTime()或者sendMessageAtFrontOfQueue实现消息入队的操作,唯一的区别就是post系列方法在消息发送前调用了getPostMessage方法:

private static Message getPostMessage(Runnable r) {  
Message m \= Message.obtain();  
m.callback \= r;  
return m;  
}  

需要注意的是:sendMessageAtTime()再被其他sendXxx调用时,典型用法为:

sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  

若调用者没有指定延迟时间,则消息的执行时间即为当前时间,也就是立即执行。Handler所暴露的方法都遵循这种操作,除非特别指定,msg消息执行时间就为:当前时间加上延迟时间,本质上是个时间戳。当然,你也可以任意指定时间,这个时间稍后的消息插入中会用到。 代码很简单,就是讲调用者传递过来的Runnable回调赋值给message(用处在消息处理中讲)。 sendMessageAtTime()sendMessageAtFrontOfQueue方法都会通过enqueueMessage方法实现消息的入栈:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,  
long uptimeMillis) {  
msg.target \= this;  
msg.workSourceUid \= ThreadLocalWorkSource.getUid();  
  
if (mAsynchronous) {  
msg.setAsynchronous(true);  
        }  
return queue.enqueueMessage(msg, uptimeMillis);  
    }  

代码很简单,主要有以下操作:

消息进入消息队列

入队前的准备工作

enqueueMessage方法是消息加入到MessageQueue的关键,下面分段来分析一下:

boolean enqueueMessage(Message msg, long when) {  
if (msg.target \== null) {  
throw new IllegalArgumentException("Message must have a target.");  
    }  
//...省略下文代码  
}      

这端代码很简单:判断message的target是否为空,为空则抛出异常。其中,target就是上文Handler.enqueueMessage里提到到Handler引用。 接下来下来开始判断和处理消息

boolean enqueueMessage(Message msg, long when) {  
//...省略上文代码  
synchronized (this) {  
if (msg.isInUse()) {  
throw new IllegalStateException(msg + " This message is already in use.");  
        }  
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;  
//...省略下文代码  
    }  
//...省略下文代码  
}  

首先加一个同步锁,接下来所有的操作都在synchronized代码块里运行 然后两个if语句用来处理两个异常情况:

  1. 判断当前msg是否已经被使用,若被使用,则排除异常;
  2. 判断消息队列(MessageQueue)是否正在关闭,如果是,则回收消息,返回入队失败(false)给调用者,并打印相关日志

若一切正常,通过markInUse标记消息正在使用(对应第一个if的异常),然后设置消息发送的时间(机器系统时间)。 接下来开始执行插入的相关操作

将消息加入队列

继续看enqueueMessage的代码实现

boolean enqueueMessage(Message msg, long when) {  
//...省略上文代码  
synchronized (this) {  
//...省略上文代码  
//步骤1  
Message p \= mMessages;  
boolean needWake;  
//步骤2  
if (p \== null || when \== 0 || when < p.when) {  
msg.next \= p;  
mMessages \= msg;  
needWake \= mBlocked;  
            } else {  
needWake \= mBlocked && p.target \== null && msg.isAsynchronous();  
//步骤3  
Message prev;  
for (;;) {  
prev \= p;  
p \= p.next;  
if (p \== null || when < p.when) {  
break;  
                    }  
if (needWake && p.isAsynchronous()) {  
needWake \= false;  
                    }  
                }  
msg.next \= p;  
prev.next \= msg;  
            }  
  
if (needWake) {  
nativeWake(mPtr);  
            }  
    }  
//步骤4  
return true;  
}  

首先说明MessageQueue使用一个单向链表维持着消息队列的,遵循先进先出的软解。 分析上面这端代码:

  1. 若表头为空,说明队列内没有任何消息,msg直接作为链表头部;
  2. when == 0 说明消息要立即执行(例如 sendMessageAtFrontOfQueue方法,但一般的发送的消息除非特别指定都是发送时的时间加上延迟时间),msg插入作为链表头部;
  3. when < p.when,说明要插入的消息执行时间早于表头,msg插入作为链表头部。

需要注意代码中的needWakenativeWake,它们是用来唤醒当前线程的。因为在消息取出端,当前线程会根据消息队列的状态进入阻塞状态,在插入时也要根据情况判断是否需要唤醒。

接下来就是从消息队列中取出消息了

从消息队列里取出消息

依旧是先看看准备准备工作

准备工作

在非主线程中使用Handler,必须要做两件事

  1. Looper.prepare():创建一个Loop
  2. Looper.loop():开启循环

我们先不管它的创建,直接分段看啊循环开始的代码:首先是一些检查和判断工作,具体细节在代码中已注释

 public static void loop() {  
//获取loop对象  
final Looper me \= myLooper();  
if (me \== null) {  
//若loop为空,则抛出异常终止操作  
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
        }  
if (me.mInLoop) {  
//loop循环重复开启  
Slog.w(TAG, "Loop again would have the queued messages be executed"  
+ " before this one completed.");  
        }  
//标记当前loop已经开启  
me.mInLoop \= true;  
//获取消息队列  
final MessageQueue queue \= me.mQueue;  
//确保权限检查基于本地进程,  
Binder.clearCallingIdentity();  
final long ident \= Binder.clearCallingIdentity();  
final int thresholdOverride \=  
SystemProperties.getInt("log.looper."  
+ Process.myUid() + "."  
+ Thread.currentThread().getName()  
+ ".slow", 0);  
boolean slowDeliveryDetected \= false;  
//...省略下文代码  
}          

loop中的操作

接下来就是循环的正式开启,精简关键代码:

 public static void loop() {  
//...省略上文代码  
for (;;) {  
//步骤一  
Message msg \= queue.next();  
if (msg \== null) {  
//步骤二  
return;  
            }  
//...省略非核心代码  
try {  
//步骤三  
msg.target.dispatchMessage(msg);  
//...  
            } catch (Exception exception) {  
//...省略非核心代码  
            } finally {  
//...省略非核心代码  
            }  
//步骤四  
msg.recycleUnchecked();  
        }  
}          

分步骤分析上述代码:

MessageQueue的next方法

我们先只看第一步消息的取出,其他的在稍后小节再看,queue.next()代码较多,依旧分段来看

Message next() {  
//步骤一  
final long ptr \= mPtr;  
if (ptr \== 0) {  
return null;  
    }  
//步骤二  
int pendingIdleHandlerCount \= \-1;   
//步骤三  
int nextPollTimeoutMillis \= 0;  
//...省略下文代码  
}     

继续分析代码:

Message next() {  
//...省略上文代码  
for(;;){  
if (nextPollTimeoutMillis != 0) {  
Binder.flushPendingCommands();  
    }  
nativePollOnce(ptr, nextPollTimeoutMillis);  
//...省略下文代码     
  }  
}   

这一段比较简单:

继续看代码:

Message next() {  
//...省略上文代码  
for(;;){  
//...省略上文代码  
//开启同步锁  
synchronized (this) {  
final long now \= SystemClock.uptimeMillis();       
//步骤一  
Message prevMsg \= null;  
Message msg \= mMessages;  
//步骤二  
if (msg != null && msg.target \== null) {  
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);  
msg.markInUse();  
return msg;  
                    }  
                } else {  
//步骤六  
nextPollTimeoutMillis \= \-1;  
                }  
    }   
//...省略下文IdleHandler相关代码      
  }  
}   

分析一下代码:

剩下的代码就基本上是IdleHandler的处理和执行了,在IdleHandler小节里进行讲解,这里就不展开说明了。

消息的处理

还记得上文中loop方法中的msg.target.dispatchMessage(msg);吗? 消息就是通过dispatchMessage方法进行分发的。其中target是msg所持有的发送它的handler的引用,它在发送消息时被赋值。 dispatchMessage的源码如下:

public void dispatchMessage(@NonNull Message msg) {  
if (msg.callback != null) {  
handleCallback(msg);  
        } else {  
if (mCallback != null) {  
if (mCallback.handleMessage(msg)) {  
return;  
                }  
            }  
handleMessage(msg);  
        }  
    }  

代码很简单,通过判断Message是否有Runable来决定是调用callback还是调用handleMessage方法,交给你定义的Handler去处理。需要注意的是,callback虽然是一个Runable,但是它并没有调用run方法,而是直接执行。这说明它并没有开启新的线程,就是作为一个方法使用(如果一开始Handler使用kotlin写的话,此处或许就是一个高阶函数了)。

其他关键点

上面讲完了消息处理的主流程,接下来讲一下主流程之外的关键点源码

Loop的创建

还记得上文中的说到的在非主线程中的要调用**Looper.prepare()** **Looper.loop()**方法吗?这两个方法可以理解为初始化Loop和开启loop循环,而主线程中无需这么做是因为在app启动的main方法中,framework层已经帮我们做了。我们分别来看这两个方法:

static final ThreadLocal<Looper\> sThreadLocal \= new ThreadLocal<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));  
    }  
private Looper(boolean quitAllowed) {  
mQueue \= new MessageQueue(quitAllowed);  
mThread \= Thread.currentThread();  
    }      

这里首先使用了一个静态的ThreadLocal确保Loop的唯一性,同时做到线程隔离,使得一个线程有且只有一个Loop实例。接着初始化Loop,同时创建MessageQueue (quitAllowed设置是否允许退出)。在这一步实现了Loop和消息队列的关联。

需要注意的是,Loop的构造方式是私有的,我们只能通过prepare 区创建,然后通过myLooper方法去获取。

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

ThreadLocal.get源码:

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();  
    }  
  
ThreadLocalMap getMap(Thread t) {  
return t.threadLocals;  
    }      

可以看到,每个Thread都持有一个ThreadLocalMap ,它和HashMap使用相同的数据结构,使用ThreadLocal作为key值,value就是Loop实例。不难发现:我们只能获取到当前线程的Loop实例。

Loop也提供了主线程中初始化的办法prepareMainLooper ,但是这个方法明确说明不允许调用,只能由系统自己调用。 这基本上就是Loop创建的关键了,也是在这里完成了Loop和消息队列以及线成之间的关联。

Handler的创建

Handler的构造函数有以下几个:

  1. public Handler()
  2. public Handler(Callback callback)
  3. public Handler(Looper looper)
  4. public Handler(Looper looper, Callback callback)
  5. public Handler(boolean async)
  6. public Handler(Callback callback, boolean async)
  7. public Handler(Looper looper, Callback callback, boolean async)

其中第一个和第二个已经被废弃了,实际上第1~5个构造方法都是通过调用public Handler(Callback callback, boolean async)public Handler(Looper looper, Callback callback, boolean async)实现的,它们的源码如下:

 public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {  
mLooper \= looper;  
mQueue \= looper.mQueue;  
mCallback \= callback;  
mAsynchronous \= async;  
    }  
public Handler(@Nullable 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) {  
//注意这个异常,loop不能为空的,首先要Looper.prepare();  
throw new RuntimeException(  
"Can't create handler inside thread " + Thread.currentThread()  
+ " that has not called Looper.prepare()");  
        }  
mQueue \= mLooper.mQueue;  
mCallback \= callback;  
mAsynchronous \= async;  
    }  

两个方法最大的区别就是一个使用传递过来的loop,一个直接使用当前线程的loop,然后就是相同的一些初始化操作了。这里就出现了一个关键点handler处理消息所处的线程和创建它的线程无关,而是和创建它时loop的线程有关的

这也是Handler能实现线程切换的原因所在: handler的执行跟创建handler的线程无关,跟创建looper的线程相关,假如在子线程中创建一个Handler,但是Handler相关的Looper是主线程的,那么如果handler执行post一个runnable,或者sendMessage,最终的handle Message都是在主线程中执行的。

Message的创建、回收和复用机制

我们可以直接使用new关键字去创建一个Message:

这些方法除了形参有些区别,用来给message不同的成员变量赋值之外,本质上都是通过 obtain()来创建Message:

public static final Object sPoolSync \= new Object();  
Message next;  
private static Message sPool;  
  
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();  
    }  

这端代码很简单,Message内部维持了一个单线链表,使用sPool作为头部,用来存储Message实体。可以发现,每次调用者需要一个新的消息的时候,都会先从链表的头部去取,有消息就直接返回。没有消息才会创建一个新的消息。

那么这个链表是在何时插入消息的呢?接下来看Message的回收:

public static final Object sPoolSync \= new Object();  
private static final int MAX\_POOL\_SIZE \= 50;  
  
void recycleUnchecked() {  
flags \= FLAG\_IN\_USE;  
what \= 0;  
arg1 \= 0;  
arg2 \= 0;  
obj \= null;  
replyTo \= null;  
sendingUid \= UID\_NONE;  
workSourceUid \= UID\_NONE;  
when \= 0;  
target \= null;  
callback \= null;  
data \= null;  
  
synchronized (sPoolSync) {  
if (sPoolSize < MAX\_POOL\_SIZE) {  
next \= sPool;  
sPool \= this;  
sPoolSize++;  
            }  
        }  
    }  

该方法在每次消息从MessageQueue 队列取出分发时都会被调用,就是在上文提到的Loop.loop()方法里。 代码也很简单,首先将Message的成员变量还原到初始状态,然后采用头插法将回收后的消息插入到链表之中(限制了最大容量为50)。而且插入和取出的操作,都是使用的同一把锁,保证了安全性。

注意插入和取出都是对链表的头部操作,这里和消息队列里就不太一样了。虽然都是使用单向链表,回收时使用头插和头取,先进后出,是个栈。而在MessageQueue里是个队列,遵循先进先出的原则,而且插入的时候是根据消息的状态确定位置,并没有固定的插入节点。

这是一个典型的享元模式,最大的特点就是复用对象,避免重复创建导致的内存浪费。这也是为什么android官方推荐使用这种方式创建消息的原因:就是为了提高效率减少性能开销。

IdleHandler

IdleHandler 的定义很简单,就是一个定义在MessageQueue里的接口:

  public static interface IdleHandler {  
boolean queueIdle();  
    }  

根据官方的解释,在 Looper循环的过程中,每当消息队列出现空闲:没有消息或者没到任何消息的执行时间需要滞后执行的时候,queueIdle 方法就会被执行,而其返回的布尔值标识IdleHandler 是永久的还是一次性的:

它的使用方法如下:

Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {  
@Override  
public boolean queueIdle() {  
return true;  
            }  
        });  

看一下addIdleHandler 的实现

private final ArrayList<IdleHandler\> mIdleHandlers \= new ArrayList<IdleHandler\>();  
  
public void addIdleHandler(@NonNull IdleHandler handler) {  
if (handler \== null) {  
throw new NullPointerException("Can't add a null IdleHandler");  
        }  
synchronized (this) {  
mIdleHandlers.add(handler);  
        }  
    }  

代码很简单,就是一个List来保存接口的实现。那么它是怎么实现在出现空闲时调用呢?

还记得在上文MessageQueue的next方法中省略的代码吗?

Message next() {  
  
//...省略不相关代码  
  
//步骤一  
int pendingIdleHandlerCount \= \-1; // -1 只存在第一次迭代中  
for (;;) {  
//...省略不相关代码  
  
//步骤二  
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)\];  
                }  
mPendingIdleHandlers \= mIdleHandlers.toArray(mPendingIdleHandlers);  
            }  
  
//步骤五  
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;  
        }  
    }  

接下来分步骤分析一下代码:

这就是IdleHandler 的核心原理,它只在消息队列为空时,或者消息队列的头部消息为延时消息时才会被触发。当消息队列头部为延时消息时,它只会触发一次哦。在前文中取消息的小节中我们讲过:延时消息在结束当前循环后进入下一路循环会触发阻塞。

Handler在Framework层的应用

不知道你有没有想过为什么Android在主线程里直接帮你调用了Looper.prepare()Looper.loop()方法,难道只是为了你使用方便吗?这岂不是有点杀鸡用牛刀的感觉?

事实上远没有这么简单,如果你看一下framework的源码你就会发现,整个android app的运转都是基于Handler进行的。四大组件的运行,它们生命周期也是基于Handler事件模型进行的,以及点击事件等。这一切均是由Android系统框架层产生相应的message再交由一个Handler进行处理的。这个Handler就是ActivityThread内部类H,贴一段它的代码截图

可以看到,四大组件的生命周期甚至内存不足,都有handler在参与。

这也解释了为什么在主线程执行耗时任务会导致UI卡顿或者ANR:因为所有主线程也就是UI线程的逻辑代码都是在组件的生命周期里执行的,而生命周期又受到Handler的事件体系的控制,当在任意生命周期做中执行耗时操作,这会导致消息队列MessageQueue中后面的消息无法得到及时的处理,就会造成延迟,直至视觉上的卡顿,严重的则会进一步触发ANR的发生

上一篇下一篇

猜你喜欢

热点阅读