关于 HandlerThread 这个类。可能有些人眼睛一瞟,手指放在键盘上,然后就是一阵狂敲。立即就能敲出一段段华丽的代码:

HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()){
public void handleMessage(Message msg) {
...
}
};
handler.sendMessage(***);

细致一看。没问题啊(我也没说代码有问题啊),那请容许我说一句,“这代码敲也敲完了,原理懂不?”

为什么要扯这玩意。没什么理由,就是不小心看了这篇文章Android消息循环机制源代码分析。学姐说了,源代码都没看,没分析。还敢说你懂。原本还认为自己懂了点,看完这句话。顿时就不确定了。

于是,自觉打开了 AS …

前言

首先,先给各位看官打个预防针。待会要讲的东西可能有点多,有点绕。涉及的类包括有:HandlerThread、Thread、Handler、Looper、Message、MessageQueue,可能有些人已经遭不住啦,有种想要关闭网页的冲动。不要慌,刚開始我看源代码的时候我也不知道最終会牵扯这么一大串出来,但细致理一理后,事实上就是那么回事。

一、擒贼先擒王 HandlerThread

这件事情的源头都是因它而起的。不先找它先找谁。

首先,HandlerThread 是什么gui。感觉像是 Handler 和 Thread 的结合体。点进源代码一看:public class HandlerThread extends Thread {} 没什么好说的,原来是一个线程的子类。那么接下来就要看看这个 HandlerThread 究竟有什么特殊之处。

HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();

跟正常线程的创建、启动步骤一样。线程已启动,那势必会运行其 run() 方法。为了方便以下流程的分析,这里先用代码块1表示:

#HandlerThread.java

public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

当中。Looper 就代表我们常常说的消息循环,Looper.prepare() 就代表消息循环运行前的一些准备工作。

二、抓捕各种小弟(Looper、MessageQueue、Message)

既然上面已经谈到Looper。那就来看一下它的几个方法:Looper.prepare() 和 Looper.loop()。

代码块2

#Looper.java

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();
} static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;

上面一路下来还是挺清晰的。总结一下:由于一个 Thread 仅仅能相应有一个 Looper,所以仅仅有满足条件下才会将 Looper 对象存放在类型为 ThreadLocal 的类属性里。当然在这之前还是要先 new 一个 Looper 对象,而在 Looper 的构造方法中又创建了两个对象 。分别为mQueue(消息队列)和 mThread(当前线程)。

整个 prepare 过程事实上主要是创建了三个对象:Looper、MessageQueue、Thread。

好了,Looper.prepare() 这个过程已经分析完了。

接着我们再看代码块1。里面有一段同步代码块,目的是为了获取 Looper 对象。方法跳转过去一看。原来就是将之前存进 ThreadLocal 里的Looper 对象取出 。

#Looper.java

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

接下来最关键的就是 Looper.loop() 这句代码,它也是 HandlerThread 这个类存在的价值所在。仅仅要一运行这句代码,也就代表真正的消息循环開始啦:代码块3

#Looper.java

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

这种方法的作用就是開始从消息队列中循环取出消息。那这个消息队列又从哪来的呢,还记得我们在Looper.prepare() 中创建 Looper 对象的时候在其构造方法中 new 两个对象嘛。一个 MessageQueue,一个Thread。

在这里要想使用消息队列。首先须要先获取 Looper 实例。毕竟消息队列 MessageQueue 是作为其成员属性而存在的。接着获得了消息队列的对象,并进入一个貌似死循环的控制流中。

这个 for 语句干的事情就是不断的从消息队列 MessageQueue 里取出消息,然后发送出去。

详细谁来处理这些消息立即揭晓。

以下的代码就是不断地取出消息:

#Looper.java

Message msg = queue.next()

接下去的内容可能就须要各位看官对数据结构有点了解了,我们一步一步嵌进去看一下。代码块4

#MessageQueue.java

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 (false) Log.v("MessageQueue", "Returning message: " + msg);
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 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)) {
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);
}
...
}
}

其他能够先无论。我们直接跳转到 synchronized 这同步代码块里。我们看到Message msg = mMessage; 这个 mMessage 对象就是一个消息 Message,仅仅只是这个 Message 类里面附带了一种数据结构:链表。我们最好还是看一下这个类:

public final class Message implements Parcelable {
...
// sometimes we store linked lists of these things
/*package*/ Message next;
...
}

不难看出。Message 类中包括了一个 Message 类型的属性,作用就是指向下一条消息。依次类推,最終形成一个链表结构。仅仅只是这里链表中的消息是要经过特殊处理的,并非每进来一条消息就直接追加到尾部。由于 Android 系统中的消息是有时间机制的。每条消息都会附加一个时间。这也是handler.sendMessageDelayed() 存在的意义。

接着看代码块4,先是对 msg 和 msg.target 进行推断,仅仅要链表中中的消息不为空,同一时候消息的触发时间小于当前系统的时间,那么这个消息就会被取出来作为待发送的对象。这里 msg.target 是非常重要的,我们之所以能 handleMessage 全靠它,以下会分析到。

然后回到代码块3,通过

Message msg = queue.next(); // might block

拿到消息后。再由 msg.target 将消息分发出去

msg.target.dispatchMessage(msg);

那这个 msg.target 究竟是个什么东西。看属性定义

/*package*/ Handler target;

竟然是一个 Handler,通过它将消息分发出去。我们再看一下是如何分发的:

#Handler.java

/**
* 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);
}
}

是不是有种茅塞顿开的感觉。

在这我就直接透漏一下,Message 中的 callback 事实上就是一个 Runnable 对象。handleCallback(msg); 就是运行其 run() 方法。

这也是为什么我们能够通过 handler.post(new Runnable(){...})来发送消息,事实上就是把Runnable对象赋给了Message的Callback 属性。

而假设是正常的 handler.sendMessage(),那么肯定就是运行以下的语句咯。我们能够在创建 Handler 对象的时候指定一个回调接口 Callback。

当然不指定也没事。我们最終还是能够通过 handleMessage(msg) 来获取待处理的消息。最后,我们还是要对这条消息进行回收重用的嘛msg.recycleUnchecked();

好了,关于Looper.prepare() 和 Looper.loop() 这两个方法就介绍到这。

我们再来补充一下,消息队列之所以有消息,那肯定得有谁提供瑟。答案就是 Handler。我们常常的操作就是handler.sendMessage(msg);代码块5

#Handler.java

public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
} public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
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);
}
#MessageQueue.java

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("MessageQueue", 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;
} 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);
}
}
return true;
}

我认为我也没什么好说的,赤裸裸的将流程一步一步的贴出来。一句话,对传入进来的 Message 进行封装,什么 msg.when、msg.target。通通在这里搞定。

如今细致回忆 Looper.loop() 里面对 msg 的处理。之前的各种❓是不就烟消云散啦。后面就顶多就是将消息加入到消息链表中。同一时候如我前面所说的那样。要依据 msg.when 的时间插入到合适的位置中去。

总结

至此,整个消息循环机制就分析完啦。原始代码:

HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()){
public void handleMessage(Message msg) {
...
}
};
handler.sendMessage(***);

再看一下详细操作流程:

  1. handlerThread.start() -> Looper.prepare() -> Looper.loop() -> queue.next() -> msg.target.dispatchMessage(msg) -> handleMessage(msg)
  2. handler.sendMessage(msg) -> queue.enqueueMessage(msg)

由于上面的一切操作都是在一个新线程的 run() 方法中运行,所以不会堵塞 UI 线程。分析完成。

这时可能有些人就站出来了,这 HandlerThread 感觉也没啥啊,我直接用 Thread 也能够搞定一切。设想一下,加入如今有10个后台任务须要运行,依照传统的做法就是运行10遍 new Thread(某个Runnable对象).start() 首先你是创建了10个匿名对象。这资源消耗多少暂且不说。你还不能非常好的控制它们。这样非常easy造成内存泄漏。若是将这些后台任务打包成成一个个 Message 然后再发送出去。首先是线程能够得到重用,再者我们还能够 remove 掉消息队列中的消息,再一定程度上避免了内存泄漏。

好了,该说的都说完了。可能须要各位看官自己脑补一下、消化一下。

Android HandlerThread 消息循环机制之源代码解析的更多相关文章

  1. Android的消息循环机制 Looper Handler类分析

    Android的消息循环机制 Looper Handler类分析 Looper类说明   Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...

  2. Android Handler 消息循环机制

    前言 一问起Android应用程序的入口,很多人会说是Activity中的onCreate方法,也有人说是ActivityThread中的静态main方法.因为Java虚拟机在运行的时候会自动加载指定 ...

  3. 安卓中的消息循环机制Handler及Looper详解

    我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handl ...

  4. Win32消息循环机制等【转载】http://blog.csdn.net/u013777351/article/details/49522219

    Dos的过程驱动与Windows的事件驱动 在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别: DOS程序主要使用顺序的,过程驱动的程序设计方法.顺序的,过程驱动的程序有一个 ...

  5. Dart异步与消息循环机制

    Dart与消息循环机制 翻译自https://www.dartlang.org/articles/event-loop/ 异步任务在Dart中随处可见,例如许多库的方法调用都会返回Future对象来实 ...

  6. 【Dart学习】-- Dart之消息循环机制[翻译]

    概述 异步任务在Dart中随处可见,例如许多库的方法调用都会返回Future对象来实现异步处理,我们也可以注册Handler来响应一些事件,如:鼠标点击事件,I/O流结束和定时器到期. 这篇文章主要介 ...

  7. Android View 事件分发机制 源码解析 (上)

    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...

  8. 理解Windows消息循环机制

    理解消息循环和整个消息传送机制对Windows编程十分重要.如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方. 什么是消息(Message)每个消息是一个整型数值,如果查 ...

  9. 详谈Windows消息循环机制

    一直对windows消息循环不太清楚,今天做个详细的总结,有说错的地方,请务必指出. 用VS2017新建一个win32 Application的默认代码如下: 程序入口                ...

随机推荐

  1. luogu1208 尼克的任务

    倒着推就是了 #include <iostream> #include <cstdio> #include <vector> using namespace std ...

  2. 【SDOJ 3741】 【poj2528】 Mayor's posters

    Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...

  3. Leetcode 457.环形数组循环

    环形数组循环 给定一组含有正整数和负整数的数组.如果某个索引中的 n 是正数的,则向前移动 n 个索引.相反,如果是负数(-n),则向后移动 n 个索引. 假设数组首尾相接.判断数组中是否有环.环中至 ...

  4. Python字典类型、

    字典类型: # msg_dic = {#     'apple': 10,#     'tesla': 100000,#     'mac': 3000,#     'lenovo': 30000,# ...

  5. Swift 3:新的访问控制fileprivate和open

    在swift 3中新增加了两种访问控制权限 fileprivate和 open.下面将对这两种新增访问控制做详细介绍. fileprivate 在原有的swift中的 private其实并不是真正的私 ...

  6. 【Luogu】P3979遥远的国度(树链剖分)

    题目链接 不会换根从暑假开始就困扰我了……拖到现在…… 会了还是很激动的. 换根操作事实上不需要(也不能)改树剖本来的dfs序……只是在query上动动手脚…… 设全树的集合为G,以root为根,u在 ...

  7. 【Luogu】P2764最小路径覆盖(拆点求最大匹配)

    题目链接 这个……学了一条定理 最小路径覆盖=原图总点数-对应二分图最大匹配数 这个对应二分图……是什么呢? 就是这样 这是原图 这是拆点之后对应的二分图. 然后咱们的目标就是从这张图上跑出个最大流来 ...

  8. UVa——540Team Queue(STL练习map、queue数组的综合使用)

    Team Queue Time Limit:                                                        3000MS                 ...

  9. HDU-2236 无题II

    选取不同列不同行的N个数...明摆着叫你二分匹配 二分答案,然后枚举边的范围并跑匈牙利,以此判断答案范围. #include <cstdlib> #include <cstdio&g ...

  10. docker 容器详解

    Docker 是一个开源的应用容器引擎,基于Go语言 并遵Apache2.0协议开源,也是一种虚拟化技术.让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的 Linux ...