阅读此文,请先阅读以下相关连接:
1. Android 消息处理机制之一: Handler与Message
2. Android 消息处理机制之二: Message中obtain()源代码剖析
在前面的两件中我们学习了Handler和Message的基本使用方法[Android消息处理机制之Handler与Message],还有Message中obtain()的几种重载方法的使用方式的不同[Android消息处理机制 Message中 btain()源代码剖析],这一讲我们来学习一下Handler中的sendMessage()的几种重载方法的使用方式的不同以及它们的源代码的剖析.
通过前面几讲的内容,我们知道Android不但可以使用异步任务处理多线程的问题,还可以通过Handler + Message + Thread 的方式进行,例如更新主线程UI等.整个架构图如下所示:
下面我就通过一个Demo来学习一下Handler中sendMessage()方法的几种重载方法,以及跟踪它们的源代码来知道它们之间的关系。
1. 使用Handler中的sendEmptyMessage(int what)方式来发送消息.
点击按钮发送消息,在Handler中做消息的处理。只发送一个带有what属性的消息。
在Handler中将消息取出打印在控制台中case R.id.button1: new Thread(new Runnable() { // 查看Handler的api,它有几种sendMessage()的方式 @Override public void run() { // 使用public final boolean sendEmptyMessage (int what) mHandler.sendEmptyMessage(3); } }).start(); break;
[分析源代码]:// Handler 可以接受或者发送消息,从消息队列中提取消息,用户更新UI的操作 protected static Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { System.out.println("--> what: " + msg.what); } };
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
它是调用sendEmptyMessageDelayed(what, 0),同时指定延时为0ms,继续跟踪它;
Message.obtain();代码上一讲研究过了,它是Message对象池中获取到Message对象,然后赋值what属性值,接着调用sendMessageDelayed(msg, delayMillis),此时delayMillis是0ms.public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
在这里做延时的处理,跟踪源码或者查看SystemClock的api文档知道SystemClock.uptimeMillis()是获取系统从开机启动到现在的时间,期间不包括休眠的时间,这里获得到的时间是一个相对的时间,而不是通过获取当前的时间(绝对时间),android之所以要这样设计的目的,待会儿会讲解到。继续跟踪.public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
这里就是Handler最终发送消息的动作,从这里可以看出,所有的消息都是在这里进行入队的操作的,当消息队列(MessageQueue)不为空的时候,指定消息对象是本身,然后入队,入队成功后返回布尔值sent,如果消息成功的放置在消息队列(message queue)中的话,sent就返回为true,如果失败则返回false.继续跟踪queue.enqueueMessage(msg, uptimeMillis).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在接受到一个消息之后,根据发送消息的时间来进行队内的轮转计算,同时也会考虑到机子处于休眠状态,阻塞情况的算法。final boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null && !mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit"); } final boolean needWake; synchronized (this) { if (mQuiting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } else if (msg.target == null) { mQuiting = true; } msg.when = when; //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up } else { Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } } if (needWake) { nativeWake(mPtr); } return true; }
【备注】:其实我们只要知道它是如何入队的就可以了,至于入队之后消息已经推送到消息队列中去了,消息在消息队列中的流程走向主要是操作系统在控制了.
【总结】:从这里我们也可以知道发送空消息在底层上的实现并不是没有消息体,它还是会从消息池中获取消息对象,赋值what属性的
2. 使用Handler中的sendEmptyMessageAtTime(int what,long uptimeMillis)方式来发送消息.
只发送一个带有what属性的消息,并且在一个指定的时间内(单位是ms)去发送。
case R.id.button1: new Thread(new Runnable() { // 查看Handler的api,它有几种sendMessage()的方式 @Override public void run() { // 使用public final boolean sendEmptyMessageAtTime (int what, long uptimeMillis) mHandler.sendEmptyMessageAtTime(3, 1000); } }).start(); break;
[源码分析]:查看源码我们可以发现它也是调用路线与上诉类似: sendEmptyMessageAtTime(int what, long uptimeMillis) ---> sendMessageAtTime(msg, uptimeMillis)完成消息的入队.
3. 使用Handler中的sendEmptyMessageDelayed (int what, long delayMillis)方式来发送消息.
[源码分析]: 查看源码我们可以发现它的调用线路: sendEmptyMessageDelayed(int what, long delayMillis) ---> sendMessageDelayed(Message msg, long delayMillis) --> sendMessageAtTime(Message msg, long uptimeMillis) --> 完成入队操作。case R.id.button1: new Thread(new Runnable() { @Override public void run() { /* * 使用public final boolean sendEmptyMessageDelayed (int what, long delayMillis) * 发送带有what属性值的消息,在延时3秒钟后发送 */ mHandler.sendEmptyMessageDelayed(3, 3000); } }).start(); break;
这里通过源码我们可以知道sendEmptyMessageAtTime()与sendEmptyMessageDelayed()的区别就是延时在时间会做 "SystemClock.uptimeMillis() + delayMillis"的方式的处理,也就是获得到系统启动开机的时间到当前的时间(不包括休眠的时间) + 延时的时间。
【注意】:这里Android为什么要采用从系统开机到现在的时间来作为时间基准,博文后面会详细讲到,这里先佩服一下Android开发工程师设计的缜密之处!
4. 使用Handler中的sendMessage (Message msg)方式来发送消息.
发送一个消息到消息队列的对尾,它会在处理这个时间的线程中的handleMessage(Message),方法中被接受到并且处理。
[源码分析]:case R.id.button1: new Thread(new Runnable() { // 查看Handler的api,它有几种sendMessage()的方式 @Override public void run() { /* * 使用public final boolean sendMessage (Message msg) */ // 使用Message.obtain()获取message对象 // Message msg = Message.obtain(); // 使用Handler获取message对象,两种获取都可以 Message msg = mHandler.obtainMessage(); msg.arg1 = 1; msg.what = 3; msg.obj = "AHuier"; mHandler.sendMessage(msg); } }).start(); break;
可见代码中两种获取Message对象的方式是一样的public final Message obtainMessage() { return Message.obtain(this); }
跟踪sendMessage()的源码,他们调用路线:sendMessage(Message msg) ---> sendMessageDelayed(Message msg, long delayMillis) ---> sendMessageAtTime(Message msg, long uptimeMillis)完成消息的入队.
【总结】: 结合上面分析的内容,我们可以知道android 中发送消息不管是Message中的几种重载的obtain()方式,还是Handler中的几种重载的sendMessage最终都是通过Handler.sendMessage来发送的,而Handler中的几种sendMessage()重载方法最终都会调用到sendMessageAtTime()方法来完成消息的入队操作。
[更新]--------------------------------------------------
在上面我们讲到sendEmptyMessageAtTime()与sendEmptyMessageDelayed()的区别(其实底层还是sendMessageAtTime()与sendMessageDelayed()这两种方法的区别)时候讲到获得当前时间的基准在这边是采用系统开机时间到当前的时间(不包括休眠的时间),这个时间是通过SystemClock.uptimeMillis()来获得的,而不是直接使用系统当前的时间?
前者是相对时间来计算,后者是绝对时间计算获得,不管是绝对的还是相对,当前获得的都是一样的时间。也就是说这两句是等效的,都是延时1秒将消息加入列队
msgHandle.sendMessageAtTime(msg, SystemClock.uptimeMillis()+1000); msgHandle.sendMessageDelayed(msg, 1000)
我自己的理解是: Android之所以用这种方式来计算时间而不是绝对的获得当前时间的目的是因为Handler的处理过程始终都会受到阻塞,等待的一些操作。Handler是绑定线程用来处理消息,难免会遇到阻塞,等待等情况,所以不应该是用绝对时间来处理,也就是说在睡眠的情况下,可能就是挂起状态而不会去处理了,如果是绝对时间的话,那就是直接剥夺抢占了。
那么如果我要在11月30日凌晨1点执行的话,应该是 SystemClock.uptimeMillis() + (11月30日凌晨1点 - 当前的时间)转换成毫秒) 不过这是理论上的值,它会考虑到手机的是否休眠然后再计算的。
Demo源码下载地址:HandlerMessageTest3