Android消息机制
每一个Android应用在启动的时候都会创建一个线程,这个线程被称为主线程或者U
I线程,Android应用的所有操作默认都会运行在这个线程中。
但是当我们想要进行数据请求,图片下载,或者其他耗时操作时,是不可能在这个UI
线程做的,因为Android在3.0以后的版本已经禁止了这件事情,直接抛出一个异常。所以我们需要一个子线程来处理那些除UI
操作的事情。
但是这个又会有一个问题,我们只能在UI
线程进程UI
操作,只能在子线程进行耗时操作,如果我们需要在耗时操作结束后在Android界面上显示一个View,我们应该怎么做?我们是不可能在子线程直接刷新UI
的。这时我们需要用到Android的消息机制,来实现主线程和子线程的通信。简单来说,就是子线程获取到数据之后,不直接进行UI
更新,而是把数据装到消息中发送到主线程,主线程中有一个循环轮询会立即收到子线程发过来的信息,然后拿到消息数据后在主线程更新UI
。说起来比较简单,我们来仔细的看一下具体是怎么说的。
处理消息的手段——Handler, Looper, MessageQueue
Handler
我们先讲解一下Handler,Handler顾名思义就是处理者,通常对他的用法是在UI
线程中新建一个Handler
,并覆写他的handleMessage
, 然后再耗时的线程中将消息post
给UI
线程,例子如下:
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg){
//更新UI
}
}
MyHandler mHandler = new MyHandler();
new Thread(){
public void run(){
mHandler.sendEmptyMessage(123);
};
}.start();
这里规定了Handler
必须在主线程创建,因为只有在UI
线程创建才会让Handler
关联到已有的MessageQueue
。而MessageQueue
被封装到Looper
中,而Looper
又通过ThreadLocal
封装到一个线程中,最后相当于MessageQueue
关联了一个线程。所以简单来说就是Handler将消息投递到一个关联了线程的MessageQueue
中,然后Handler
在从MessageQueue
中取出消息,并且处理它。
我们看一下Handler的2个常用的方法
void handleMessage(Message msg) :
处理消息的方法
final boolean sendMessage(Message msg) :
立即发送消息
第一个方法 我们通常在UI
线程中执行,一般用来刷新UI
,至于如果创建了一个非静态内部类产生对内存泄漏,建议参考这篇博客Handler引发的内存泄漏.第二个方法我们通常在子线程中执行,需要一个Handler的实例化对象,通常是由主线程去去传递给子线程。并且需要一个Message对象,指定他的msg.what
作为消息的标示,但是如果我们只是用Handler去处理一个消息的时候,选择post方法是个更好的选择,例子如下:
private Handler mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
//UI操作
...
}
});
}
}).start();
下面我们接着讨论下消息的循环队列MessageQueue
与包装他的Looper
循环
Looper和MessageQueue
上面提到了在UI
线程中创建并实例化Handler对象不需要Looper
和MessageQueue
,因为我们的应用在启动的时候先执行了ActivityThreadMain
,在这个方法就是Java语言运行的入口public static void main(String [] args)
在这里面创建了一个MainLooper
,创建的过程如下:
public static void main(string[] args){
//初始化
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
//动起来
Looper.loop();
}
这里面并没有MessageQueue的出现,我们可以看一看Looper类的源码,来了解在初始化的时候发生了什么有趣的事情。
public class Looper {
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
Thread mThread;
// 。。。其他属性
// 每个Looper对象中有它的消息队列,和它所属的线程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 我们调用该方法会在调用线程的TLS中创建Looper对象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}
我们一行行的看这段代码,首先是实例化一个ThreadLocal对象,这个用来实现Looper
循环的本地化存储,关于ThreadLocal
可以看这篇文章为什么用ThreadLocal,简而言之就是当多个线程同时访问Looper
对象的时候,我们不用synchronized
同步机制来处理他,而是为每个线程创建一个自己的Looper
副本,A线程改变了A的looper
副本,不影响B线程的Looper
,从而比较高效的实现线程安全。后面几句依次定义了MessageQueue
,并对Looper进行了私有化构造,在prepare
方法中将Looper对象设置给了sThreadLocal
这样MessageQueue
包装在了Looper对象中,同时通过ThreadLocal
使得线程和Looper关联上,从而消息队列与线程关联上,并且不同的线程就不能访问对方的消息队列。
如下图所示:
接着就是Looper.loop
循环执行起来,我们看一下,在loop
方法里面执行了发生了什么事情
public static final void loop() {
Looper me = myLooper(); //得到当前线程Looper
MessageQueue queue = me.mQueue; //得到当前looper的MQ
while (true) {
Message msg = queue.next(); // 取出message
if (msg != null) {
if (msg.target == null) {
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
这是省略版的代码,我们从这里看出无限循环执行,首先从消息队列中不断取出消息,然后不断msg
是否为空,msg.target
是否为空,不空的话,执行dispatchMessage
方法,这个方法是handler的一个方法,由此我们可以看出msg.target
是handler
的类型,至此,通过Looper.prepare
和Loop.loop
实现了MessageQueue,Looper,Handler
三者之间的关联。而Handler
与Looper
,和MessageQueue
关联则是在Handler的默认构造器中,通过Looper.getLooper
获取loop对象,从而获取MessageQueue
,其源码如下:
public Handler(){
//直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = null;
}
然后我们的流程图可以多些内容,如下所示:
我们接下来看一下dispatchMessage()
方法,在该方法中实际上只是一个分发方法,如果Runable
类型的callback
为空,则执行handlerMessage
来处理消息,该方法为空,需要覆写。如果不为空,则执行handleCallback
。实际上,如果我们用handle
的post
方法,则就执行了callback
,如果用sendMessage
,则就执行了handleMessage
这里无论是post(Runnable callback)
还是handlerMessage
实际上都是在调用一个方法sendMessageDelayed(Message msg)
只不过handlerMessage
是直接接受一个参数,而Runable
callback
实际上是将这个Runable
对象赋给了Message
对象的callback
成员变量,最后将Message
对象插入消息队列里面。最后Looper不断从MessageQueue中读取消息,并且调用Handler的dispatchMessage消息,在根据callback是否为空,来采用不同的方法执行。Android消息机制分析到此结束。
回到最开始
我们这次知道了为什么要在主线程中实例化Handler
对象才能更新UI
刷新,因为只有发送到UI
线程的消息,才能被UI
线程的handler
处理,如果我们要在非UI
线程中,实例化Handler,则必须先将线程变成LooperThread
,在实例化。也就是说执行如下的代码:
Loop.prepare();
hander = new Handler;
Loop.loop
至于原因相信读完上面的讲解,应该知道。
现在我们看一下我们最开始的代码,最后脑补一下Handler的工作流程。
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg){
//更新UI
}
}
MyHandler mHandler = new MyHandler();
new Thread(){
public void run(){
mHandler.sendEmptyMessage(123);
};
}.start();
在Handler
实例化成mHandler
的时候,系统通过Handler
默认的构造函数完成了Handler
与Looper
的关联,并通过Looper
关联到了MessageQueue
。而主线程的Looper
则早在系统启动的时候通过Loop.prepare
就已经构造完成了,并与UI
线程通过ThreadLocal
关联起来,然后在新的线程中执行mHandler.sendEmptyMessage
,将Message
发送给了MessageQueue
,Looper.loop在循环的时候,不断取出message,交给Handler处理,在我们覆写的HandleMessage中,识别出我们发送的消息,将消息处理。当然这里只是一个Empty消息,所以在handleMessage中没有去执行msg.what的判断。
Android消息机制的更多相关文章
- Android消息机制:Looper,MessageQueue,Message与handler
Android消息机制好多人都讲过,但是自己去翻源码的时候才能明白. 今天试着讲一下,因为目标是讲清楚整体逻辑,所以不追究细节. Message是消息机制的核心,所以从Message讲起. 1.Mes ...
- Android消息机制不完全解析(上)
Handler和Message是Android开发者常用的两个API,我一直对于它的内部实现比较好奇,所以用空闲的时间,阅读了一下他们的源码. 相关的Java Class: androi ...
- Android消息机制不完全解析(下)
接着上一篇文章Android消息机制不完全解析(上),接着看C++部分的实现. 首先,看看在/frameworks/base/core/jni/android_os_MessageQueue.cpp文 ...
- Android 消息机制 (Handler、Message、Looper)
综合:http://blog.csdn.net/dadoneo/article/details/7667726 与 http://android.tgbus.com/Android/androidne ...
- Android开发之漫漫长途 ⅥI——Android消息机制(Looper Handler MessageQueue Message)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- 【腾讯Bugly干货分享】经典随机Crash之二:Android消息机制
本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 承上经典随机Crash之一:线程安全 问题的模型 好几次灰度top1.top2 Crash发生场景:在很平常.频繁的使用页面,打开一个界面,马 ...
- Android开发之漫漫长途 Ⅶ——Android消息机制(Looper Handler MessageQueue Message)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- android 进程间通信 messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯? android 消息机制 进程间 android 进程间 可以用 handler么 messenger 与 handler 机制 messenger 机制 是不是 就是 handler 机制 或 , 是不是就是 消息机制 android messenge
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯 ...
- Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)
不要心急,一点一点的进步才是最靠谱的. 读完本文你将了解: 前言 Message 如何获取一个消息 Messageobtain 消息的回收利用 MessageQueue MessageQueue 的属 ...
随机推荐
- BZOJ1742[Usaco2005 nov]Grazing on the Run
Description John养了一只叫Joseph的奶牛.一次她去放牛,来到一个非常长的一片地,上面有N块地方长了茂盛的草.我们可 以认为草地是一个数轴上的一些点.Joseph看到这些草非常兴奋, ...
- Tomcat如何配置环境变量
1, JDK:版本为jdk-7-windows-i586.exe 下载地址: http://www.oracle.com/technetwork/java/javase/downloads/index ...
- android studio 华为手机看不到具体的错误日志
手机的开发人员选项打开了么,其中的 USB 调试打开了么?搞定他们并重新运行,是否能找到我们的日志?否,转到 2. 日志中是否有这样 could not disable core file gener ...
- 《photon中配置lite的相关问题》
前几天在学习photon的时候发现了一个问题: 无论如何都找不到Lite文件夹,我是一个新手这也是写给那些新上手的朋友: 首先下载SDK以后配置完成后无论如何都找不到Lite文件夹和相关的Lite.d ...
- GDB常用命令
一. gdb使用流程 1.编译生成可执行文件 gcc -g hello.c -o hello 2.启动gdb gdb hello 3. 在main处设置断点 break main 4.运行程序 run ...
- C.C++把整个文件内容读进一个buffer中
原创文章,未经本人允许禁止转载. //C方式, 调用的函数繁多 //fopen,fseek,ftell,fseek,malloc,fread,fclose,free. void foo() { FIL ...
- 【算法与数据结构】二叉搜索树的Java实现
为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨. 首先,二叉搜索树是啥?它有什么用呢? 二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指 ...
- 黑马程序员-nil Nil NULL NSNull 野指针和空指针
空指针1.空指针指不含有任何内存地址的指针.在没有具体初始化之前,其被符值为0Dog * dog = nil;Dog * dog = NULL;都为空指针2.野指针指指向的内存为垃圾内存,导致其值不确 ...
- 第58讲:Scala中Abstract Types实战详解
这一讲我们来学习下抽像类型.让我们看下代码 package scala.learnimport scala.io.BufferedSourceimport scala.io.Source trait ...
- 重新开源UDS
这个题目起得很纠结. 因为很多人都知道UDS本来就是开源,我只不过改了一些东西,然后重新发布,所以不算重新开源. 要说重新发布也不对.因为老早这东西就发布了. 最后我想,这个东西已经很久没更新过了,也 ...