您的位置:首页 > 运维架构

Handler的消息运行处理机制

2016-12-06 15:10 603 查看

一、Handler运行机制的总体流程



由上图可以看出,对于handler处理消息的流程是:

①:产生消息:消息队列中产生了新的消息

②:获取消息:创建handler对象,获取到要发送的消息

③:发送消息:通过handler机制,将消息发送到looper的队列中

④:处理消息:looper的轮循机制,不断的扫描查看是否有新的消息产生,如果发现有新的消息产生,那么就调用该handler对应的handler处理方法,进行消息处理。


对于handler处理消息的机制,大致就是以上几个过程,当然在执行流程之前,我们已经建立了Handler和Looper的对象。下面我们就从源码的角度详细的分析整个执行过程。

二、Handler关联Looper对象

单链表结构是怎样形成的呢?这个就是由我们的消息的创建机制来完成的。主要是实现把消息池里的第一条数据取出,当消息池中有消息时,这时我们就需要调用handler对象来进行获取消息和发送消息,那么hanlder的对象究竟是怎样获取和发送消息的呢?这时我们就需要查看源码了。

/**
* Default constructor associates this handler with the queue for the
* current thread.
*
* If there isn't one, this handler won't be able to receive messages.
*/
public Handler() {
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();//调用myLooper()方法获取looper对象
if (mLooper == null) {//如果mLooper为空,就会报运行时异常,导致程序崩掉。
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//拿到消息队列
mCallback = null;//将回调对象设置为空。
}


通过上面的源码分析,我们可以看出,Handler主要是完成了获取looper对象和拿到消息队列。那么究竟是怎样获取到这两个对象的呢?我们还是要从源码角度分析:

/**
* Return the Looper object associated with the current thread.  Returns
* null if the calling thread is not associated with a Looper.
*/
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();//通过sThreadLocal的get方法获取到looper对象
}


通过上面的源码分析,我们可以看出静态方法myLooper()主要是通过get方法获取到looper对象,既然有get方法,那么肯定会存在一个set方法,将looper对象设置进去,继续跟踪源码。

/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}

从上面的源码中我们可以看出Looper是直接new出来的,并且继续跟踪源码我们发现在Looper的构造方法中,new出了消息队列对象

private Looper() {
mQueue = new MessageQueue();//创建消息队列
mRun = true;
mThread = Thread.currentThread();//获取到当前线程对象
}


既然ThreadLocal.set(new Looper())方法是Looper.prepare()调用的,那么,prepare()又是什么时候调用的呢?继续查看源码,会发现是prepareMainLooper()调用的,至此我们便查看到了整个looper的创建和调用机制,如果继续在Looper类中查找我们,很难发现什么时候调用prepareMainLooper()方法,这个时候我们需要查看ActivityThread类,在其main()方法中我们会发现该方法,下面我们对该方法继续分析:

public static final void main(String[] args) {
...
//创建Looper和MessageQueue
Looper.prepareMainLooper();
...
//轮询器开始轮询
Looper.loop();
...
///如果上面的looper结束,那么在此立刻就会抛异常,程序停止
}


三、Looper的轮循机制

细心的哥们会发现,对于Looper的轮循是通过调用Looper.loop()实现的,该方法中具体是实现那些事情呢?我们查看源码一起分析一下。

/**
*  Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static final void loop() {
Looper me = myLooper();//获取looper对象
MessageQueue queue = me.mQueue;//获取队列
while (true) {//死循环,不断的轮循
Message msg = queue.next(); <
4000
span class="hljs-comment">// 取出下一个消息
//if (!me.mRun) {
//    break;
//}
if (msg != null) {//如果消息不为空
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);//分发消息
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to    " + msg.target + " "
+ msg.callback);
msg.recycle();//回收消息
}
}
}


这里面通过一个死循环来不断的轮序消息队列,查看是否有消息的产生,如果有消息产生,就分发消息,然后轮循消息。有哥们会问“这个处理是在主线程中,不会出现ANR吗?”,针对于这一点我们详细分析一下。

为什么采用死循环?

我们都知道hanlder的处理机制中一个重要的环节就是通过建立Looper的机制,不断的轮循消息队列,查看是否有消息产生,如果有,就将消息去除,交给对应的handler方法处理(message对象的target属性,用于记录该消息由哪个Handler创建,在obtain方法中赋值),如果不是采用死循环的形式,我们无法保证looper循环的连贯性。

ANR产生的原因

在主线程中做耗时操作,肯定会产生”ANR”,在我们该方法中,主要可能产生“ANR”的是msg.target.dispatchMessage(msg),因为对于消息的分发处理,如果某个消息是一个耗时的操作,那么就有可能产生“ANR”,导致阻塞对于下一个消息的处理。

怎样解决ANR

假如我在oncreate()开启一个耗时操作,如果是在里面直接开启耗时,那么就会阻塞其他方法的执行(onstart()),其他的消息就无法正常的执行和处理,但是如果我们在此时开启一个子线程去处理oncreate()中的方法,那么oncreate()就会立即结束,主线程就会执行其他的方法中,不会产生ANR,当oncreate()的耗时操作,执行结束后,通过sendMessage()方法,将消息发送给主线程,主线程收到消息后再处理。这样整个流程就会正常执行。

解决死循环的机制

Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两个句柄(引用),一个是读取句柄,一个是写入句柄,主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。

四、Message的队列维护和发送

我们提到了Message的是单链表结构,那么这种结构究竟是怎样产生的呢?我们继续跟踪源码去查看一下。Handler发送消息,sendMessage的所有重载,实际最终都调用了sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}


分析源码可以看出,对于消息的队列维护的处理,主要是通过sent = queue.enqueueMessage(msg, uptimeMillis)方法完成的,其中两个参数分别表示的是消息对象和时间戳。下面仔细分析一下这个加载维护过程。

4.1消息入列

注意观察msg.when = when;其中when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志。

synchronized (this) {
...
//对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比
msg.when = when;//这个when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志
Message p = mMessages;
//把放入消息队列的消息置为消息队列第一条消息
if (p == null || when == 0 || when < p.when) {//注意when,时间早的放在最前面
msg.next = p;//mMessages指向该消息对象
mMessages = msg;//指向新的消息
needWake = mBlocked; // new head, might need to wake up//是否换到主线程
} else {//下个消息....
//判断时间顺序,为刚放进来的消息寻找合适的位置
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
} msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up
}
}




第一个消息进入

如图所示,当我们要加载一个消息(每个消息都包含when 和next属性)进入队列时,mMessages指向该消息,直接加入到队列中。

再次有消息进入

再次有消息进入时,会将mMessages赋值给临时变量p,那么p就指向了队列中的消息对象。此时就开始比较when的值(假如新加入的消息when小于队列中的,走条件一),注意下面的赋值变换:

msg.next = p;//p指向新来的消息的next

mMessages = msg;//新来的消息指向队列中的message。

同理走else条件时,仍然是根据when条件进行判断,从而形成了单链表结构。

4.2 回收消息

/**
* Return a Message instance to the global pool.  You MUST NOT touch
* the Message after calling this function -- it has effectively been
* freed.
*/
public void recycle() {
synchronized (mPoolSync) {
if (mPoolSize < MAX_POOL_SIZE) {
clearForRecycle();//调用消息的方法

next = mPool;//原先指向队列中消息的mpool指向新进入的消息的next
mPool = this;//将当前对象赋值给mpool
}
}
}


实现原理:如果当前队列中的mPoolSize 小于队列能承受的最大值,就将消息添加到message的队列中,否则就将消息丢掉,交给垃圾回收机制处理。

如果符合条件,就将原来指向队列中的mPool赋值刚进入的next,新加入的消息对象赋值给mPool。

4.2获取消息

获取消息的机制如下图所示:



源码分析

/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (mPoolSync) {
if (mPool != null) {
Message m = mPool;
mPool = m.next;
m.next = null;
return m;
}
}
return new Message();
}


obtain()方法中mPool(临时的Message对象)判断是否为空,如果不为空,则进入内部的判断。具体实现原理解析:

- Message m = mPool;//临时变量m指向第一个消息对象

- mPool = m.next;//mPool 指向下一个对象

- m.next = null;//m的next为空,就是将其断开

这样就完成了消息队列的取出机制。

至此我们就完成了整个的handler消息产生、发送、looper的轮循处理、交给对应handler方法处理等过程。

总结:主线程中有一个Looper机制里面包含一个while死循环,这个死循环运行的原理以及解决ANR的方法;创建Handler怎样和Looper进行关联的。其次讲解了从Message队列中怎样获取到Message消息,获取到消息后是怎样通过handler的机制加入到Looper的可执行队列中的(按when排序)。最后我们是怎样调用对应的Handler方法执行的。

备注:码农小白,水平有限,如有错误欢迎指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息