Android之消息机制Handler,Looper,Message解析
PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧。。
学习内容:
1.MessageQueue,Looper,MessageQueue的作用.
2.子线程向主线程中发送消息
3.主线程向子线程中发送消息
异步消息处理机制是Android提供给我们异步更新UI的一种很好的方式,线程之间以Handler作为桥梁,使得Message可以在线程间进行传递,从而实现异步的一种方式。
1.MessageQueue
MessageQueue顾名思义,指的就是消息队列,说这个之前我们首先需要知道什么是Message,比如说我们在UI界面时点击一个按钮,或者是接收到了一条广播,其实都算是一条Message,这些事件都被封装成一条Message被添加到了MessageQueue队列当中,因为我们知道一个线程在一段时间只能对一种操作进行相关的处理,因此这些消息的处理就要有先后顺序,因此采用MessageQueue来管理,也就是消息队列。消息队列其实就是一堆需要处理的Message而形成的传送带。一旦有消息发送进来,那么直接执行enqueueMessage()方法。也就是将消息压入到队列当中,一旦线程空闲下来,那么直接从MessageQueue中取出消息,使得消息出队。
2.Looper
Looper的主要作用是与当前线程形成一种绑定的关系,同时创建一个MessageQueue,这样保证一个线程只能持有一个Looper和MessageQueue同时Looper使得MessageQueue循环起来,就好比水车和水一样,MessageQueue好比水车,当他有了Looper的时候,那么水车会随着水去转动也就是说Looper为MessageQueue提供了活力,使其循环起来,循环的动力往往就少不了Thread。一般而言子线程一般是没有MessageQueue,因此为了使线程和消息队列能够关联那么就需要有Looper来完成了,因此我们可以这样去实现
class ChildRunnable implements Runnable{ @Override
public void run() {
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.e("TAG",msg.obj+"");
}
};
Log.e("TAG_2",Looper.myLooper().toString());
Looper.loop(); }
}
这里调用了Looper的prepare()和loop()方法。
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
方法比较的简单,我们可以看到prepare()方法首先判断了sThreadLocal中持有的线程引用是否为空,如果不为空,那么直接就会抛异常,这也就说明了Looper.prepare()在一个线程中只允许调用一次,这样也同样为一个线程对应一个Looper做了保障。当Looper.prepare()执行完毕之后Looper才可以执行loop()方法。
public static void loop() { /**
* 获取当前线程绑定的Looper
*/
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
/**
* 当前线程的MessageQueue
*/
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(); /**
* 死循环,循环从MessageQueue取出消息.
*/
for (;;) {
/**
* 从Queue中取出一条消息
*/
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.recycle();
}
}
这里我们可以看到loop()是通过几个流程使得MessageQueue循环起来的,首先通过静态方法获取当前线程对应的Looper,然后获取由Looper创建的MessageQueue,然后进入死循环,阻塞式的从MessageQueue中取出消息,获取到消息之后将消息分发出去,否则进入等待状态,等待新的消息到来。这就是Looper使MessageQueue循环起来的方式。
那么我们现在MessageQueue消息队列已经存在了,Looper以线程为动力使得MessageQueue循环了起来,那么消息的发送和处理由谁来完成呢显而易见这就是Handler的作用了.
3.Handler
Handler的出现使得消息可以被发送和接收,Handler的构造方法有多个。
public Handler() { } public Handler(Handler.Callback callback) { } public Handler(Looper looper) { } public Handler(Looper looper, Handler.Callback callback) { }
这是Handler的四个构造方法,1和2都没有传递Looper,那么Handler通过调用Looper.myLooper()方法将Handler与Looper和MessageQueue形成绑定关系,而3和4直接传递Looper,那么Handler将直接将传递进来的Looper对象进行保存,直接和传递的Looper以及相关的MessageQueue形成绑定关系。同时这四个构造方法还有不同点,就是是否传递Callback回调接口对应的参数。
public interface Callback {
public boolean handleMessage(Message msg);
}
实现Callback接口是实现处理Message的一种方式,而另一种方式则不传递Callback回调接口,而是直接实现Handler中handleMessage方法,也就是说我们可以通过这两种方式实现Handler对消息的处理。这样三者就形成了绑定关系,然后我们来看看Handler的sendMessage等方法.其实无论是调用了哪种方法,sendMessage(),sendMessageDelayed()等等,最后都会调用sendMessageAtTime()方法。这里需要先说一下post方法和sendMessage方法。一般我们使用Handler发送消息的时候也会这样去写。
handler.post(new Runnable() {
@Override
public void run() {
/**
* 相关操作
* */
}
});
这样发送消息也是可以的,post执行过程是这样的。
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
} private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
post方法也是发送了一条消息,runnable则作为callback参数作为回调,用于后续处理消息。这里也调用了sendMessageDelayed()方法,最后还是会调用sendMessageAtTime()方法,因此可以看出,无论是send还是post,最后都会调用sendMessageAtTime()方法。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
sendMessageAtTime()是最终调用的方法,他通过调用enqueueMessage将消息加入到消息队列当中。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//注意下面这行代码
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//注意下面这行代码
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage方法比较的简单,这里讲msg.target = this,将msg的target赋值为当前的handler,然后调用queue.enqueueMessage()方法将消息加入到消息队列当中。那么SendMessage之后,Looper就通过loop()方法不断的从MessageQueue取出消息,然后通过dispatchMessage()方法将消息分发给Handler,交给Handler去处理相关的Message.
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这样我们就可以直接去处理相关的消息了,这个方法做了一些判断,也就是判断是否传递了callback属性,如果msg.callback不为空,那么则表示是post发送过来的消息,在处理的时候就需要调用handleCallback(msg)方法,然后我们会继续判断Handler的Callback是否为空,也就是我们在new Handler(Handler.Callback)方法去创建Handler的时候的判断,如果不为空,那么执行我们Callback中实现的handleMessage()方法,如果为空,那么就直接执行Handler中的handleMessage()方法。总体的流程就是这样。在其他的牛人博客中,看到了一幅特别形象的流程图。在这里贴出来。
在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端进行收集处理。
我们可以把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是Looper,而发动机的转动是需要电源的,我们可以把电源看做是线程Thread,所有的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们只需要按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时候,我们就相当于执行了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。
那Hanlder在传送带模型中相当于什么呢?我们可以将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,我们就相当于调用了Hanlder的dispatchMessage方法,在该方法中我们完成对Message的处理。
2.子线程向主线程发送消息
子线程向主线程发送消息,平时还是很常用的,比如说子线程完成了一个耗时的操作,需要主线程去更新UI,那么这个时候就需要子线程向主线程中发送相关的消息。用起来也比较的简单。
private Handler handler = new Handler(){
//主线程做处理.
@Override
public void handleMessage(Message msg) {
String data = (String) msg.obj;
textView.setText("从子线程发送过来的消息"+data);
childRunnable = null;
}
};
class ChildRunnable implements Runnable {
//子线程发送相关消息
@Override
public void run() {
Message message = Message.obtain();
message.obj = "ChildToMain";
handler.sendMessage(message);
}
}
3.主线程将消息发送给子线程
我们经常将消息从子线程发送给主线程,其实主线程也是可以发消息给子线程的。在默认情况下子线程是没有Looper和MessageQueue的,因此我们需要为子线程创建一个Looper然后与子线程的Handler形成绑定关系。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_main_to_child);
Log.e("TAG_0",Looper.getMainLooper().toString());
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Message message = Message.obtain();
message.obj = "MainToChild";
handler.sendMessage(message);
}
});
new Thread(new ChildRunnable()).start(); } class ChildRunnable implements Runnable{ @Override
public void run() {
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.e("TAG",msg.obj+"");
}
};
Log.e("TAG_2",Looper.myLooper().toString());
Looper.loop(); }
}
这里设置了一个按钮的监听事件来控制主线程向子线程发送消息,或者我们先让主线程休眠,先让子线程初始化完毕之后,主线程再向子线程发送消息,如果直接发送的话,很有可能在Handler还没有初始化完毕后就导致消息发送,这样就会出现Handler为null从而导致NullPointerExpection发生。因此在这里进行了简单的控制,这里我们可以看到,如果子线程想拥有自己的Looper和MessageQueue首先需要执行Looper.prepare()和Looper.loop()方法。才能为子线程的Handler绑定上Looper和MessageQueue。有人可能会问道,为什么主线程直接发送消息就可以,而不需要调用这两个方法,这是因为Activity在onCreate的时候已经执行了这些方法,因此我们可以直接发消息给主线程。
因此当我们在使用Handler Message Looper实现异步消息机制的时候,如果想实现多级通信,那么就需要弄明白当前的Looper MessageQueue绑定的是哪个Handler。Looper,MessageQueue在一个线程中只有唯一一个,但是Handler可以是多个的,我们只需要控制这个绑定关系是实现多级通信的关键。
最后贴一个简单的源代码:Demo下载
Android之消息机制Handler,Looper,Message解析的更多相关文章
- 【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理
ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻 ...
- Android的消息机制简单总结
参考文章: http://gityuan.com/2015/12/26/handler-message-framework/#next 参考资料: Android Framework的源码: Mess ...
- 聊一聊Android的消息机制
聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比 ...
- 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制
第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...
- Android的消息机制
一.简介 ①.我们不能在子线程中去访问UI空控件,这是时候只能通过Handler将更新UI的操作放到主线程中去执行 ②.Handler的组成:messageQueue和Looper的支持 ③.Mess ...
- 【原创】源码角度分析Android的消息机制系列(一)——Android消息机制概述
ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.为什么需要Android的消息机制 因为Android系统不允许在子线程中去访问UI,即Android系统不允许在子线程中更新UI. 为什 ...
- Android 基础 十一 Android的消息机制
Handler是Android消息机制的上层接口,这使得在开发应用过程中我们只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去 ...
- Android开发——Android的消息机制详解
)子线程默认是没有Looper的,Handler创建前,必须手动创建,否则会报错.通过Looper.prepare()即可为当前线程创建一个Looper,并通过Looper.loop()来开启消息循环 ...
- Android10_原理机制系列_Android消息机制(Handler)详述
概述 在Android中的多进程.多线程中提过,只有主线程(UI线程)可以更新UI,其他线程不可以,所以一般耗时操作放到子线程.子线程可以通过Handler将相关信息通知到主线程. Android的消 ...
随机推荐
- java实现全排列
前天上午的面试遇到了一个用java实现一串数字的全排列的题,想来想去用递归最方便,可是没有在规定的时间内完成555,今天上午有空便继续写,以下是完成后的代码: import java.util.Arr ...
- 初识 Html5
1.1认识HTML5 HTML5并不仅仅只是做为HTML标记语言的一个最新版本,更重要的是它制定了Web应用开发的一系列标准,成为第一个将Web做为应用开发平台的HTML语言. HTML5定义了一系列 ...
- UnderScore源代码阅读1
读一下underscore源代码,用于自己学习,个人理解,如果有不对的地方希望指正,谢谢 我觉着阅读的顺序按照从整体到局部,从架构到细节较好. 1.整体架构 (function() {}.call(t ...
- 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现
[实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...
- .net core 学习
资源: https://github.com/aspnet/home https://github.com/dotnet/cli
- Cisco VPN can't work in Win8
Goto HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CvirtA Change DisplayName to "Cisco ...
- 将word文档A表格中的内容拷贝到word文档B表格中
Function IsFileExists(ByVal strFileName As String) As Boolean ) <> Empty Then IsFileExists = T ...
- logistic回归
logistic回归 回归就是对已知公式的未知参数进行估计.比如已知公式是$y = a*x + b$,未知参数是a和b,利用多真实的(x,y)训练数据对a和b的取值去自动估计.估计的方法是在给定训练样 ...
- Base 64 编码
原创地址:http://www.cnblogs.com/jfzhu/p/4020097.html 转载请注明出处 (一)Encoding VS. Encryption 很多人都以为编码(Encodin ...
- 在Windows中安装Memcached
Memcached是一个高并发的内存键值对缓存系统,它的主要作用是将数据库查询结果,内容,以及其它一些耗时的计算结果缓存到系统内存中,从而加速Web应用程序的响应速度. Memcached最开始是作为 ...