安卓中的消息循环机制Handler及Looper详解
我们知道安卓中的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详解的更多相关文章
- Android的消息循环机制 Looper Handler类分析
Android的消息循环机制 Looper Handler类分析 Looper类说明 Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...
- Android Handler 消息循环机制
前言 一问起Android应用程序的入口,很多人会说是Activity中的onCreate方法,也有人说是ActivityThread中的静态main方法.因为Java虚拟机在运行的时候会自动加载指定 ...
- Dart异步与消息循环机制
Dart与消息循环机制 翻译自https://www.dartlang.org/articles/event-loop/ 异步任务在Dart中随处可见,例如许多库的方法调用都会返回Future对象来实 ...
- Android HandlerThread 消息循环机制之源代码解析
关于 HandlerThread 这个类.可能有些人眼睛一瞟,手指放在键盘上,然后就是一阵狂敲.立即就能敲出一段段华丽的代码: HandlerThread handlerThread = new Ha ...
- 【Dart学习】-- Dart之消息循环机制[翻译]
概述 异步任务在Dart中随处可见,例如许多库的方法调用都会返回Future对象来实现异步处理,我们也可以注册Handler来响应一些事件,如:鼠标点击事件,I/O流结束和定时器到期. 这篇文章主要介 ...
- Win32消息循环机制等【转载】http://blog.csdn.net/u013777351/article/details/49522219
Dos的过程驱动与Windows的事件驱动 在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别: DOS程序主要使用顺序的,过程驱动的程序设计方法.顺序的,过程驱动的程序有一个 ...
- Looper.loop() android线程中的消息循环
Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Loope ...
- TMsgThread, TCommThread -- 在delphi线程中实现消息循环
http://delphi.cjcsoft.net//viewthread.php?tid=635 在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使 ...
- TMsgThread, TCommThread -- 在delphi线程中实现消息循环(105篇博客,好多研究消息的文章)
在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供. 花了两天的事件研究了 ...
随机推荐
- Unix系统的文件目录项的内容是什么,这样处理的好处是什么?
(Unix系统采用树型目录结构,而且目录中带有交叉勾链.每个目录表称为一个目录文件.一个目录文件是由目录项组成的.) 每个目录项包含16个字节,一个辅存磁盘块(512B)包含32个目录项.在目录项中, ...
- Vue2学习结合bootstrapTable遇到的问题
Vue2学习 项目中在使用bootstrapTable的时候,在table里面会有操作结合vue使用过程中点击相应的操作不会起作用 解决办法 1.把事件绑定到父元素上即可,但要判断什么样的需要点击,用 ...
- flask jQuery ajax 上传文件
1.html 代码 <div> <form id="uploadForm" enctype="multipart/form-data" > ...
- SQL Server 2008 维护计划实现数据库备份(最佳实践)
一.背景 之前写过一篇关于备份的文章:SQL Server 维护计划实现数据库备份,上面文章使用完整备份和差异备份基本上能解决数据库备份的问题,但是为了保障数据更加安全,我们需要再次完善我们的备份计划 ...
- numpy.squeeze()是干啥的
例子: a = 3 print np.squeeze(a) # 输出3 a = [3] print np.squeeze(a) # 输出3 a = [[3]] print np.squeeze(a) ...
- 我与android的缘分
android的开始 本人是一名大三的学生,大一大二主要学习的是php后台开发,在大一的时候做过一些小的网站系统,也参加过一些大学生计算机相关的比赛.这次开始着手于安卓开发,也是一时的兴起.因为跟我们 ...
- Response ServletContext 中文乱码 Request 编码 请求行 共享数据 转发重定向
Day35 Response 1.1.1 ServletContext概念 u 项目的管理者(上下文对象),服务器启动时,会为每一个项目创建一个对应的ServletContext对象. 1.1.2 ...
- 原生js去掉所有的html标签,最终得到HTML标签中的所有内容
替换掉所有的 html标签,最终得到Html标签中的内容 <script> //替换掉所有的 html标签,最终得到Html标签中的内容 var req="<div sty ...
- Python安装与环境变量的配置
python下载: Python安装包下载地址:http://www.python.org/ 根据实际的操作系统,安装合适的安装版本. Python安装: 本文以python 2.7.8(64位)为例 ...
- JavaScript的BOM、DOM操作、节点以及表格(二)
BOM操作 一.什么是BOM BOM(Browser Object Model)即浏览器对象模型. BOM提供了独立于内容 而与浏览器窗口进行交互的对象: BOM由一系列相关的对象构成 ...