android开发系列之消息机制
最近接触到一个比较有挑战性的项目,我发现里面使用大量的消息机制,现在这篇博客我想具体分析一下:android里面的消息到底是什么东西,消息机制到底有什么好处呢?
其实说到android消息机制,我们可能很快就会想到message/handle/looper这三个对象,没错在android里面就是通过此三个对象实现消息机制的。那么我想问的是,为什么我们需要消息机制呢?说到底就是为了更好、更流畅的用户体验,因为在app里面我们或多或少都有可能会设计到一些比较耗时的操作,这个时候如果我们只是一味的将当前所有的操作都停留在一个线程上面的话,那么该线程将特别的卡,导致用户界面会出现长时间的卡顿、没有响应的情况。那么,我们能不能聪明一点将一些耗时的操作放置在工作线程上面呢?当前是可以的,这个时候我们的主线程-UI线程,就可以只负责处理UI更新、展示的工作了,当工作线程完成之后,我们就可以通过某种机制通知UI进行更新了,说到这里你也许就能明白消息机制的产生初衷了。就是为了多个线程之间的互通互信,最常用的场景也许就是HTTP网络请求了。
好了,让我们先通过如下代码实例:
- btnMsgTest.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- WorkThread workThread = new WorkThread();
- workThread.start();
- }
- });
上面这段代码我只是订阅了一个按钮点击事件,在该按钮点击事件里面我们开启了一个新的工作线程,让我们来看看工作线程的实现代码:
- class WorkThread extends Thread {
- @Override
- public void run() {
- btnMsgTest.setText("你好");
- }
- }
其实很简单,我们只是在工作线程里面修改一下按钮的text属性值,但是当前点击按钮的时候,却发现程序报错了:
- android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
- at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
- at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:942)
- at android.view.ViewGroup.invalidateChild(ViewGroup.java:5081)
- at android.view.View.invalidateInternal(View.java:12713)
- at android.view.View.invalidate(View.java:12677)
- at android.view.View.invalidate(View.java:12661)
- at android.widget.TextView.checkForRelayout(TextView.java:7159)
- at android.widget.TextView.setText(TextView.java:4342)
- at android.widget.TextView.setText(TextView.java:4199)
- at android.widget.TextView.setText(TextView.java:4174)
- at example.xiaocai.com.testgumpcome.MainActivity$WorkThread.run(MainActivity.java:157)
- 03-05 04:15:00.741 1600-1613/example.xiaocai.com.testgumpcome E/Surface﹕ getSlotFromBufferLocked: unknown buffer: 0xee9f20e0
这是为什么呢?让我们来分析一下上面的exception提示,意思是说:UI更新只能在它自己原始的线程上面。那么问题就来了,我们怎么将工作线程里面的更新通知到UI上面呢?这个时候消息机制就可以大展拳脚了。
我们先来看一下message对象,这个对象就像理解的字面意思一样就是真正的消息对象,我们可以通过如下代码定义一个消息:
- Message message=handler.obtainMessage();
- message.what=1;
- message.obj="hello";
- handler.sendMessage(message);
注意我们这里是从利用obtainMessage方法,创建一个message对象,而不是利用new Message创建新的message对象。说点题外话,这两者有什么区别吗?通过源码分析我们会发现obtainMessage是从message池里面分配一个对象,这样就避免创建新的对象了。
细心的你,也许从上面的代码里面还能看到handler对象,下面我们就来说一说handler对象吧!我们创建好message之后,怎样发送出去呢?然后又是怎样调度处理的呢?我们可以直接在代码里面创建一个handler对象,如下代码:
- Handler handler2=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- }
- };
然后我们就可以通过该handler2对象调用sendMessage方法就可以将消息发送出去了。通过这个流程分析,我们大概就能猜出handler的作用了,它就消息的具体操作对象,通过handler我们可以发送各种类型的message,可以处理消息等。
说到这里,我们还有个疑问就是如果如果我想要发送多个message对象,那么不同的message对象又是怎样调度执行的呢?请看下面的代码:
- Message message=handler.obtainMessage();
- message.what=1;
- message.obj="hello";
- handler.sendMessage(message);
- Message message1=handler.obtainMessage();
- message1.what=2;
- message1.obj="world";
- handler.sendMessage(message1);
这种情况下,是不是就意味着后台会有个MessageQuene呢?要不然,多个消息是放在哪里呢?然后又是通过哪种方式进行message对象调度呢?没错,多个message会通过looper对象进行调度执行。
说到这里,让我们利用消息机制重新来修改最上面的实例代码:
- class WorkThread extends Thread {
- @Override
- public void run() {
- // btnMsgTest.setText("你好");
- Message message=handler.obtainMessage();
- message.what=1;
- message.obj="修改按钮文本";
- handler.sendMessage(message);
- }
- }
这个时候我们在工作线程里面就不是直接更新UI了,而是发送一个message,然后在handler里面就行UI更新,让我们来看看这块的代码:
- private Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case 1:
- btnMsgTest.setText("你好");
- break;
- }
- }
- };
看到这里也许聪明的你就会感到奇怪了,我怎么没有看到looper的身影呢?这样new出来的Handler就能确定一定是在UI线程上面吗?亲,先别着急,让我们来看看与上面同等的一段代码,也许你就明白了。
- private Handler handler2=new Handler(Looper.getMainLooper(),new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- btnMsgTest.setText("通过looper设置");
- return false;
- }
- });
在这段代码里面我们就能看到Looper的身影了,同时我们调用了getMainLooper方法获取到了主线程也就是我们需要的UI线程。看到这里我们就能够通过下面这幅图来形象的说明三者之间的关系了。
通过上面的分析,我们能够了解android消息机制到底是怎么回事,同时还能够比较熟练的使用它们。下面让我们再深入一步,研究一下android消息机制的源码,更加直观的了解到message是怎样发送、调度、处理的。首先来看看message源码:
- public final class Message implements Parcelable
我们可以看到message实现了一个Parcelable接口,该接口的主要作用就是包装、传输数据。没错,message里面我们当然需要传输数据了。然后让我们来看看message里面包括的常用属性信息代码:
- /**
- * User-defined message code so that the recipient can identify
- * what this message is about. Each {@link Handler} has its own name-space
- * for message codes, so you do not need to worry about yours conflicting
- * with other handlers.
- */
- public int what;
- /**
- * arg1 and arg2 are lower-cost alternatives to using
- * {@link #setData(Bundle) setData()} if you only need to store a
- * few integer values.
- */
- public int arg1;
- /**
- * arg1 and arg2 are lower-cost alternatives to using
- * {@link #setData(Bundle) setData()} if you only need to store a
- * few integer values.
- */
- public int arg2;
- /**
- * An arbitrary object to send to the recipient. When using
- * {@link Messenger} to send the message across processes this can only
- * be non-null if it contains a Parcelable of a framework class (not one
- * implemented by the application). For other data transfer use
- * {@link #setData}.
- *
- * <p>Note that Parcelable objects here are not supported prior to
- * the {@link android.os.Build.VERSION_CODES#FROYO} release.
- */
- public Object obj;
以上四个属性,在我们设置一个message信息的时候,可能会经常使用到,具体的时候场景大家可以在注释里面看出。最后让我们来看看主要的方法:
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static Message obtain() {
- synchronized (sPoolSync) {
- if (sPool != null) {
- Message m = sPool;
- sPool = m.next;
- m.next = null;
- sPoolSize--;
- return m;
- }
- }
- return new Message();
- }
通过上面的代码我们可以看出,如果消息池不为空的话,那么我们就同步的从消息池里面分配消息,否则的话就实例化一个message对象。这样的话,我们一般创建一个message对象的时候,就建议使用obtain方法,这样就可以很好的提高应用性能。message里面还有个回收方法,代码如下:
- /**
- * Return a Message instance to the global pool. You MUST NOT touch
- * the Message after calling this function -- it has effectively been
- * freed.
- */
- public void recycle() {
- clearForRecycle();
- synchronized (sPoolSync) {
- if (sPoolSize < MAX_POOL_SIZE) {
- next = sPool;
- sPool = this;
- sPoolSize++;
- }
- }
- }
- /*package*/ void clearForRecycle() {
- flags = 0;
- what = 0;
- arg1 = 0;
- arg2 = 0;
- obj = null;
- replyTo = null;
- when = 0;
- target = null;
- callback = null;
- data = null;
- }
我们可以看到其实在recycle方法里面实现是很简单的,只是将一些变量置为默认值,同时将消息池变大。剩下其他的方法就是实现Parcelable接口,实现数据的包装。
接下来让我们看看Handler里面最主要的方法,首当其冲的就是消息分发方法,源码如下:
- /**
- * Handle system messages here.
- */
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
大意就是说:如果我们在message里面已经写了回调方法那么就直接调用message里面的方法进行处理。如果message里面没有实现回调处理,那么Handler里面的接口回调,代码如下:
- /**
- * Callback interface you can use when instantiating a Handler to avoid
- * having to implement your own subclass of Handler.
- *
- * @param msg A {@link android.os.Message Message} object
- * @return True if no further handling is desired
- */
- public interface Callback {
- public boolean handleMessage(Message msg);
- }
这也就是为什么我们能够直接在new一个Handler的时候,复写里面的handlerMessage方法进行消息处理的原因。其次就是刚刚我们看到的构造方法,源码如下所示:
- /**
- * Use the provided {@link Looper} instead of the default one and take a callback
- * interface in which to handle messages.
- *
- * @param looper The looper, must not be null.
- * @param callback The callback interface in which to handle messages, or null.
- */
- public Handler(Looper looper, Callback callback) {
- this(looper, callback, false);
- }
通过一个Looper对象获取线程环境,然后实现Callback回调。最后比较重要的方法就是一系列发送消息的方法,源码如下:
- /**
- * Pushes a message onto the end of the message queue after all pending messages
- * before the current time. It will be received in {@link #handleMessage},
- * in the thread attached to this handler.
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting.
- */
- public final boolean sendMessage(Message msg)
- {
- return sendMessageDelayed(msg, 0);
- }
- /**
- * Sends a Message containing only the what value.
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting.
- */
- public final boolean sendEmptyMessage(int what)
- {
- return sendEmptyMessageDelayed(what, 0);
- }
- /**
- * Sends a Message containing only the what value, to be delivered
- * after the specified amount of time elapses.
- * @see #sendMessageDelayed(android.os.Message, long)
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting.
- */
- public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
- Message msg = Message.obtain();
- msg.what = what;
- return sendMessageDelayed(msg, delayMillis);
- }
- /**
- * Sends a Message containing only the what value, to be delivered
- * at a specific time.
- * @see #sendMessageAtTime(android.os.Message, long)
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting.
- */
- public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
- Message msg = Message.obtain();
- msg.what = what;
- return sendMessageAtTime(msg, uptimeMillis);
- }
- /**
- * Enqueue a message into the message queue after all pending messages
- * before (current time + delayMillis). You will receive it in
- * {@link #handleMessage}, in the thread attached to this handler.
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting. Note that a
- * result of true does not mean the message will be processed -- if
- * the looper is quit before the delivery time of the message
- * occurs then the message will be dropped.
- */
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis < 0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- /**
- * Enqueue a message into the message queue after all pending messages
- * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
- * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
- * You will receive it in {@link #handleMessage}, in the thread attached
- * to this handler.
- *
- * @param uptimeMillis The absolute time at which the message should be
- * delivered, using the
- * {@link android.os.SystemClock#uptimeMillis} time-base.
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting. Note that a
- * result of true does not mean the message will be processed -- if
- * the looper is quit before the delivery time of the message
- * occurs then the message will be dropped.
- */
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- return enqueueMessage(queue, msg, uptimeMillis);
- }
- /**
- * Enqueue a message at the front of the message queue, to be processed on
- * the next iteration of the message loop. You will receive it in
- * {@link #handleMessage}, in the thread attached to this handler.
- * <b>This method is only for use in very special circumstances -- it
- * can easily starve the message queue, cause ordering problems, or have
- * other unexpected side-effects.</b>
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting.
- */
- public final boolean sendMessageAtFrontOfQueue(Message msg) {
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- return enqueueMessage(queue, msg, 0);
- }
上面方法的不同之处就在于发送的时间、发送的一些属性内容不同罢了。
好了,让我们来看看Looper里面的源码,首先最主要的方法有prepare,从名字你也许就能够看出,该方法最主要就是准备好messagequeue,然后调用loop方法从该messagequeue里面调度message对象,源码如下:
- /**
- * Run the message queue in this thread. Be sure to call
- * {@link #quit()} to end the loop.
- */
- public static void loop() {
- final Looper me = myLooper();
- if (me == null) {
- throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
- }
- 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();
- for (;;) {
- 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();
- }
- }
大意如下:循环遍历message对象,同时分发message对象。然后还有两个比较重要的方法就是getMainLooper和getMyLooper,源码如下:
- /**
- * Initialize the current thread as a looper, marking it as an
- * application's main looper. The main looper for your application
- * is created by the Android environment, so you should never need
- * to call this function yourself. See also: {@link #prepare()}
- */
- public static void prepareMainLooper() {
- prepare(false);
- synchronized (Looper.class) {
- if (sMainLooper != null) {
- throw new IllegalStateException("The main Looper has already been prepared.");
- }
- sMainLooper = myLooper();
- }
- }
- /** Returns the application's main looper, which lives in the main thread of the application.
- */
- public static Looper getMainLooper() {
- synchronized (Looper.class) {
- return sMainLooper;
- }
- }
- /**
- * Return the Looper object associated with the current thread. Returns
- * null if the calling thread is not associated with a Looper.
- */
- public static Looper myLooper() {
- return sThreadLocal.get();
- }
好了,今天博客就先到这里吧,如果有什么不对的地方欢迎拍砖。
android开发系列之消息机制的更多相关文章
- Android 开发系列教程之(一)Android基础知识
什么是Android Android一词最早是出现在法国作家维里耶德利尔·亚当1986年发表的<未来夏娃>这部科幻小说中,作者利尔·亚当将外表像人类的机器起名为Android,这就是And ...
- Android消息传递之Handler消息机制
前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...
- Android Framework 分析---2消息机制Native层
在Android的消息机制中.不仅提供了供Application 开发使用的java的消息循环.事实上java的机制终于还是靠native来实现的.在native不仅提供一套消息传递和处理的机制,还提 ...
- Android学习笔记之消息机制
Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程. 1.为什么要使用Handler? Android规定访问UI只 ...
- Cocos2d-x游戏开发中的消息机制:CCNotificationCenter的使用
在HTML5游戏开发中,js可以使用Event对象的addEventListener(添加事件监听).dispatchEvent(触发事件)实现监听机制,如果在coocos2d-x中,去实现这种机制该 ...
- android开发系列之aidl
aidl在android开发中的主要作用就是跨进程通讯来着,说到进程相比很多人都是非常熟悉了,但是为什么会有跨进程通讯这个概念呢?原来在android系统中,有这么一套安全机制,为了各个Apk数据的独 ...
- Android Handle,Looper,Message消息机制
尊重原创,转载请标明出处 http://blog.csdn.net/abcdef314159 我们知道在Android中更新UI都是在主线程中,而操作一些耗时的任务则须要在子线程中.假设存在多个 ...
- Android Handler MessageQueue Looper 消息机制原理
提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...
- Android10_原理机制系列_Android消息机制(Handler)详述
概述 在Android中的多进程.多线程中提过,只有主线程(UI线程)可以更新UI,其他线程不可以,所以一般耗时操作放到子线程.子线程可以通过Handler将相关信息通知到主线程. Android的消 ...
随机推荐
- php get传递数据
url:?goods[]=924&goods[]=967&goods[]=993 <?php if($_GET){ print_r($_GET); } ...
- 洛谷P2728 纺车的轮子 Spinning Wheels
P2728 纺车的轮子 Spinning Wheels 29通过 66提交 题目提供者该用户不存在 标签USACO 难度普及/提高- 提交 讨论 题解 最新讨论 暂时没有讨论 题目背景 一架纺车 ...
- 用代码打开FORM里面用到的数据源
修改动态报表的时候,尝尝需要根据当前设计里指定的数据源,然后打开AOT去查找,相当的不方便. 于是产生写了一个方法,可以根据传过来的数据源名,去AOT找到TABLE或者VIEW, 直接打开,以便修改. ...
- .NET中使用log4net
一,加载log4net引用 下载log4net.dll,我们这里使用的是.NET2.0 下载地址:http://files.cnblogs.com/gosky/log4net-1.2.13-bin-n ...
- CSS3新增伪类
p:last-of-type 选择其父元素的最后的一个P元素 p:last-child 选择其父元素的最后子元素(一定是P才行) p:first-of-type ...
- zencart用chrome无法登录后台
再本地安装完zencart后,可以使用ie和Firefox登录网站后台,但是使用chrome登录时,页面闪一下,然后又跳转到登录页面. 按如下设置可以解决该问题: 中文版:商店设置->Sessi ...
- 01-实现图片按钮的缩放、动画效果(block的初步应用)
#import "ViewController.h" #define kDelta 60 @interface ViewController () @end @implementa ...
- 初识 css3中counter属性
最近看到counter属性,好奇是做什么用的,于是去查了查. 1.简单介绍 counter是为css中插入计数器.[注明]在CSS2.1中counter()只能被使用在content属性上.关于浏览器 ...
- SequoiaDB(巨杉数据库)(社区版)安装配置使用图解
SequoaiDB是一款新型企业级分布式非关系型数据库,提供了基于PC服务器的大规模集群数据平台.作为全球第一家企业级文档式 NoSQL分布式数据库,为用户提供了一个高扩展性.高可用性.高性能.易维护 ...
- Entity Framework 5问题集锦
ORM框架万万千,一直都使用NHibernate,没用过其他的.最近闲来学习下微软自家的Entity Framework,记录一些我学习过程中遇到的头疼问题.(不断更新中...) 教程:http:// ...