我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handler机制来完成该功能,即当子线程中耗时操作完成后,在子线程中通过Handler向主线程发送消息,在主线程中的Handler的handleMessage方法中处理接受到的消息。这就是安卓中的消息机制,安卓中的消息机制主要是指Handler的运行机制,但是Handler的运行需要底层的MessageQueue和Looper机制的支撑。

下面我们来详解讲解安卓中的消息循环机制,即Handler,MessageQueue,Looper机制。注意本博客属于安卓进阶内容,所以一些基础性的东西如果看官不懂情请自行百度解决。

首先我们来看一个标准的使用Handler机制的代码示范例子:

class LooperThread extends Thread {
public Handler mHandler; public void run() {
Looper.prepare(); mHandler = new Handler() {
public void handleMessage(Message msg) {
// handle messages here
}
}; Looper.loop();
}
}

这段代码大家肯定都不陌生,因为这是安卓中使用Handler机制的一个最标准的代码示范,也许看官可能会说我在使用Handler的时候没使用Looper.prepare()与Looper.loop()

语句,那时因为我们使用Handler通常是在UI线程中,而UI线程已经默认为我们做好了这些准备工作,至于这个待会后面会讲到。之所以选择在一个子线程中创建Handler的例子来讲解,是因为这样能让我们更加清楚的明白安卓消息循环中Handler,MessageQueue,Looper这三者之间的关系,因为在主线程中很多细节都被掩藏起来了。

首先从这段代码可以看到这里涉及两个概念(事实上安卓中的消息循环机制涉及三个重要概念),这里先简单介绍下

Looper:消息循环,用于从MessageQueue消息队列中取出消息进行处理。

Handler:消息发送与处理,Handler通过sendMessage向消息队列MessageQueue中发送一条消息,通过handlerMessage来处理消息

MessageQueue:消息队列,用来存放Handler发送的消息,它仅仅用来存放消息

首先讲解一下安卓中的消息循环的整体过程,然后从上述的示范代码上进行详细的讲解。

安卓中的消息循环是使用Looper这个类来实现的,而Looper是基于线程的,即一个Looper对象与创建它的线程相关联,当使用 Looper.prepare()语句时它会创建一个Looper对象和一个MessageQueue对象,然后在创建Handler时会先获取到Handler所在的线程的Looper对象(如果调用的是无参构造函数时),通过这个Looper对象同时可以获得其MessageQueue对象,正因为如此相当于Handler与Looper相关联。这样通过Handler发送的消息才知道应该通过哪个Looper去进行处理。这是理解安卓整个消息循环机制核心,即MeaageQueue与Looper相关联,Handler与Looper相关联,从这里也可以看出Looper是安卓消息循环机制的核心。

下面以上述示范代码为例进行详细讲解。

首先我们来看一下 Looper.prepare()语句,即看一下prepare的源码:

public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}

从这里可以看到prepare()函数作了两件事:创建一个looper对象,然后通过 sThreadLocal.set(new Looper());语句将其加入到 sThreadLocal中,这里就不得不提一下安卓消息循环机制中一个重要的概念:ThreadLocal,因为这涉及到Looper对象与线程相关联的功能的实现。ThreadLocal它的作用是在不同的线程中访问同一个ThreadLocal对象时通过ThreadLocal获取到的值是不一样的,即ThreadLocal中存储的值是基于线程的。我们来看一下ThreadLocal的set方法:

 public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

可以看到set函数首先获取当前的线程,然后通过当前线程产生一个Valuse对象,然后通过values的put方法将value(即Looper对象)与this(即ThreadLocal对象)相关联,通过这段代码我们应该知道Looper是与线程相关的,因为set方法会通过线程产生Valuse对象,然后通过Valuse对象put方法,将Looper保存起来。

接下来我们来看Handler handler=new Handler();这个语句,我们来看一下Handler()这个无参构造函数。

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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}

从这里我们可以看到首先调用了 Looper.myLooper();方法,该方法是返回当前线程所关联的Looper,且可以看到如果获取到的mLooper为null则会抛出异常,这也说明创建Handler之前必选先创建Handler对象,获得了Looper对象之后,我们就可以获取到与Looper相关联的MessageQueue对象,即 mQueue = mLooper.mQueue;前面讲过创建Looper时会创建消息队列MessageQueue。这段代码我们重点来看一下Looper.myLooper();这个方法,通过这个函数获取到了与当前线程相关联的Looper对象,正因为如此,Handler对象发送的消息才知道应该被那个Looper处理。我们来看一下其代码:

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

可以看到他是通过sThreadLocal.get()方法来取得Looper对象,这个get与我们上面讲述的ThreadLocal的set方法是相对应的,一个用来存储数据,一个用来取出数据。

我们来看一下sThreadLocal.get()方法:

public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
} return (T) values.getAfterMiss(this);
}

可以看到sThreadLocal.get()方法先获取当前线程,然后通过当前线程构造Values对象,然后通过valuse返回其存储的数据(当然该数据为Looper对象),也正因为如此在Handler中获取到的Looper对象与我们在当前线程中创建的Looper对象是同一个对象,这是保证Handler对象发送的信息被正确的Looper所处理的关键。

最后看一下Looper.loop();语句,这个语句的作用是开启Looper循环,我们来看一下其源代码:

public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
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();
}
}
}

可以看到loop方法首先获取当前Looper对象,然后获取该Looper对象的MessageQueue对象,然后在一个while死循环中不断的通过 queue.next();从消息队列中取出一个消息,然后通过消息msg.target这个属性来调用dispatchMessage(msg);来分派消息,msg.target这个属性本质上是发送消息给这个MessageQueue的Handler对象,即通过此语句就将Handler发送的消息交给它的dispatchMessage(msg);方法来处理,这样就将代码逻辑切换到创建该Handler的线程中去执行了。

我们来看一下Handler的dispatchMessage(msg);方法

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

可以看到在该方法中调用了 handleMessage(msg);,而这个正是我们在Handler中处理消息的回调方法。



最后就是我们使用sendMessage来发送消息的过程,Handler提供了多个发送消息的函数,最终都会调用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;
}

可以看到在该方法中最终调用了MessageQueue的enqueueMessage(msg, uptimeMillis);方法,顾名思义,其作用是将一个消息入队,它的源码不重要,我们只需知道它是通过将一个消息插入到一个单向链表中的方式来完成的入队操作即可。

在前面我们说过在主线程中我们是不需要创建一个Looper的,这是因为这些工作安卓系统几经帮我们做好了,安卓中的主线程即ActivityThread,主线程的入口函数为main,我们来看一下其源代码:

public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到在该方法中调用了Looper.prepareMainLooper()方法,而Looper.prepareMainLooper()方法会调用Looper.prepare()方法。所以在主线程中我们不需自己创建一个Looper对象。

好了,通过上述的讲解相信看官对安卓中的消息机制已经非常的清楚了,下面总结一下:

1Looper对象是安卓消息循环的核心,Looper对象是基于线程的,我们在创建一个Looper对象时会通过ThreadLocal对象将我们创建的Looper与其所在的线程相关联起来,具体来说是通过 sThreadLocal.set(new Looper());语句将其保存起来,另外在创建一个Looper对象时其内部会帮我们自动创建了一个消息队列MessageQueue对象。

2Looper的作用是通过一个while死循环来不断的查看消息队列MessageQueue中是否存在消息Message,若存在则会通过 msg.target.dispatchMessage(msg);将消息交给发送消息的Handler对象来进行处理。

3我们在创建一个Handler对象时,其内部首先会获得当前线程所关联的Looper对象,即调用 Looper.myLooper();方法,具体来说是通过sThreadLocal.get()方法来完成的,

这样就保证了在Handler中获取到的Looper对象与我们在当前线程中创建的Looper对象是同一个对象,从而保证Handler对象发送的信息被正确的Looper所处理。

4另外在创建一个Handler对象时,其内部会通过获取到的Looper对象来获取与Looper对象相关联的MessageQueue对象,这样通过sendMessage发送的消息会发送到这个MessageQueue对象中,这也是保证Handler对象发送的信息被正确的MessageQueue所处理的关键(其本质是通过3来完成的,因为MessageQueue的创建是在Looper内部完成的,即MessageQueue是与Looper相关联的)。

5MessageQueue仅仅用来保存消息而已,消息的分派是通过Looper来完成的,在Looper的loop循环中会通过一个while死循环来不断的查看消息队列MessageQueue中是否存在消息Message,若存在则会通过 msg.target.dispatchMessage(msg);将消息交给发送消息的Handler对象来进行处理,这样就将代码的逻辑切换到Handler所在的线程中进行处理。

用图示表示如下:

好了上述就是本人理解的关于安卓消息循环机制的全部内容,看官如果觉得不错请点个赞哦,也请看官多多支持本人的其它博客文章

安卓中的消息循环机制Handler及Looper详解的更多相关文章

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

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

  2. Android Handler 消息循环机制

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

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

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

  4. Android HandlerThread 消息循环机制之源代码解析

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

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

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

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

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

  7. Looper.loop() android线程中的消息循环

    Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Loope ...

  8. TMsgThread, TCommThread -- 在delphi线程中实现消息循环

    http://delphi.cjcsoft.net//viewthread.php?tid=635 在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使 ...

  9. TMsgThread, TCommThread -- 在delphi线程中实现消息循环(105篇博客,好多研究消息的文章)

    在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供.   花了两天的事件研究了 ...

随机推荐

  1. hdu 3016 dp+线段树

    Man Down Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total S ...

  2. hdu 1402 FFT(模板)

    A * B Problem Plus Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other ...

  3. bzoj3129[Sdoi2013]方程 exlucas+容斥原理

    3129: [Sdoi2013]方程 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 582  Solved: 338[Submit][Status][ ...

  4. java的泛型

    泛型概述 先看下面的代码: ArrayList al1 = new ArrayList(); ArrayList al2 = new ArrayList(); al1.add("hello& ...

  5. SpringCloud学习之sleuth&zipkin【二】

    这篇文章我们解决上篇链路跟踪的遗留问题 一.将追踪数据存放到MySQL数据库中 默认情况下zipkin将收集到的数据存放在内存中(In-Memeroy),但是不可避免带来了几个问题: 在服务重新启动后 ...

  6. Java的五子棋实现

    Java 五子棋 注:除机器人算法外其余借鉴于MLDN. package MyFiveChess; import robot.*; import java.awt.*; import javax.sw ...

  7. 如何上传本地项目到gitHub解决方案

    最近有人有人问到我怎么将新创建的本地代码上传到github上,这里简单的记录一下,我喜欢使用命令行,这里全用命令行来实现,不了解Git命令的可以去了解下. 1.  建立本地仓库,cd到你想要上传文件的 ...

  8. tf.nn.conv2d 和 tf.nn.max_pool 中 padding 分别为 'VALID' 和 'SAME' 的直觉上的经验和测试代码

    这个地方一开始是迷糊的,写代码做比较分析,总结出直觉上的经验. 某人若想看精准的解释,移步这个网址(http://blog.csdn.net/fireflychh/article/details/73 ...

  9. 0. 迷之 -> 和 .

    0. 迷之 -> 和 . 箭头(->):左边必须为指针: 点号(.):左边必须为实体. e.g.1 class class A{ public: play(); }; int main() ...

  10. python中修改字符串的几种方法

    在Python中,字符串是不可变类型,即无法直接修改字符串的某一位字符.因此改变一个字符串的元素需要新建一个新的字符串.常见的修改方法有以下4种. 方法1:将字符串转换成列表后修改值,然后用join组 ...