Handler发送sendMessage和postRunnable的区别
2016-03-10 17:31
555 查看
</pre><p class="p1"><span class="s1">先从平时的应用入手吧。试想这样一个场景,我们有一个下载文件的需求,而且我们在界面上要显示下载的状态:未下载,下载中,已下载。这个时候我们该怎么办?首先可以在界面上放一个Button,显示未下载,然后设置点击事件,点击后显示下载中,并开启一个线程去下载(这里用线程sleep代替,实际未下载)。等下载完成后,再把Button上的文字改为已下载。我们来试一下:</span></p><pre name="code" class="java">public class MainActivity extends AppCompatActivity { private Button mButton; private Thread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.bt_1); initThread(); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mButton.setText("开启下载线程下载中"); mThread.start(); mButton.setClickable(false); } }); } private void initThread() { mThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } mButton.setText("已下载完成"); } }); } }
编译运行,一切正常,然后我们点击一下按钮:肏,crash掉了。好吧,淡定,淡定,我们看一下log:
03-10 16:15:22.707 10592-11189/com.example.gray_dog3.handlertest E/AndroidRuntime: FATAL EXCEPTION: Thread-302
Process: com.example.gray_dog3.handlertest, PID: 10592
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7177)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1065)
好吧,用我过了四级的英语水平给翻译下吧,错误的线程调用异常,发生Crash的地点是ViewRootImpl中调用checkThread方法中。温馨提示是:只有创建View的线程才能对摸摸它创建的View。。。好吧,顺着代码一瞅,果然,我再mThread中摸了一把在UI线程中创建的mButton。所以报了这个错。为了更直观的看到报错原因,我们直接跳到源码ViewRootImpl的checkThread方法,看它做了什么。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
就是这货了,但是Android为什么要这样搞呢?我们仔细看这句话,只有创建了View的线程才能对这个View进行操作。而我们一般View都是为了显式在UI上的。Android正是为了防止我们在非UI线程去操作这些UI上的控件,才加了限制的。因为UI体验对用户来说是最直观的,如果谁都有权限去搞一发,那UI要么很乱,要么控制很复杂。但是总不能难搞就不让搞把?实际上是可以的,Android为我们提供一个工具,来让我们进行线程间的消息传递,就是我们接下来要讲的主角:Handler。
对于Handler来说上面的问题可以轻易解决,看代码:
<pre name="code" class="java">public class MainActivity extends AppCompatActivity { private Button mButton; private Thread mThread; private Handler mHandler; private int a=3,b=5; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler=new Handler(){ @Override public void handleMessage(Message msg) { mButton.setText("已下载完成"); } }; initThread(); mButton = (Button) findViewById(R.id.bt_1); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mButton.setText("开启下载线程下载中"); mThread.start(); mButton.setClickable(false); } }); } private void initThread() { mThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendMessage(Message.obtain()); } }); } }
在上面的代码中,我们只是在主线程里创建一个Handler,重写它的handleMessage方法,然后子线程里拿到这个Handler,下载结束后用Handler给主线程发一个消息。然后主线程收到消息后,再去改变Button上显示的文字。
上面只是Handler最普通的一个应用场景,发送消息,接收消息。实际上这也是它所有的能做的事。有人说还有postRunnable的吗?怎么能说是只有发送和就收消息呢?好,我们带着这个疑问来看下Handler究竟是什么:
首先看Android源码注释,
A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread's {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread's message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it -- from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
翻译:handler使你能够给绑定的线程发送一个消息或者一个Runnable对象。每一个Handler的实例都与一个线程和该线程的消息队列进行绑定。
从创建出一个Handler的时候,Handler就与创建出它的那个线程绑定了,它将发送消息或者Runnable对象到该线程的消息队列里面。
There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed as some point in the future;
(2) to enqueue an action to be performed on a different thread than your own.
Handler两种主要用途:
一、在将来某个时刻或者某个逻辑节点调度一个message,或者Runnable执行。说人话就是将来某个时间节点或者逻辑节点,想要做什么事可以通过Handler来做。
二、给别的线程插入一个操作。就是你想在别的线程中执行一些代码,可以拿到它的Handler给他发消息或postRunnnable。
更直观的描述:
由图中可以看出,Hanler是用来发送消息,和接收消息的,所有发送的消息都是一个Message的实例,放在MessageQueue中。Looper则负责从MessageQueue中取出消息,发送给Handler,交由Handler去处理。
上面是对Handler一些概念性的描述和结构图,让我们对Handler有了一些感性认知。要用Handler就要new一个Handler出来,接下来看看Handler都有哪些构造方法(一个无聊的过程,构造方法不就是new出一个实例么,带参数就是给响应的属性赋初始值,其实我们在看构造方法时最终要的还是要看它给哪些属性赋初始值,因为既然提出一个构造方法给一个属性赋值,说明这个属性相对来说用的比较多。一般也是研究这个类应该关注的重点):
/** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); } /** * Constructor associates this handler with the {@link Looper} for the * current thread and takes a callback interface in which you can handle * messages. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. * * @param callback The callback interface in which to handle messages, or null. */ public Handler(Callback callback) { this(callback, false); } /** * Use the provided {@link Looper} instead of the default one. * * @param looper The looper, must not be null. */ public Handler(Looper looper) { this(looper, null, false); } /** * 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); } /** * Use the {@link Looper} for the current thread * and set whether the handler should be asynchronous. * * Handlers are synchronous by default unless this constructor is used to make * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. * * @hide */ public Handler(boolean async) { this(null, async); } /** * Use the {@link Looper} for the current thread with the specified callback interface * and set whether the handler should be asynchronous. * * Handlers are synchronous by default unless this constructor is used to make * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param callback The callback interface in which to handle messages, or null. * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. * * @hide */ public Handler(Callback callback, boolean async) { 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 = callback; mAsynchronous = async; } /** * Use the provided {@link Looper} instead of the default one and take a callback * interface in which to handle messages. Also set whether the handler * should be asynchronous. * * Handlers are synchronous by default unless this constructor is used to make * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param looper The looper, must not be null. * @param callback The callback interface in which to handle messages, or null. * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. * * @hide */ public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
无聊的源码copy,可以看到最终都是调用的最后一个构造函数,Android源码里面到处都是这种写法,接下来我们要讲的sendMessage也是这种写法。这样写的好处是,我们不用关心过多的参数,灵活适配各种不同的需求。来看下初始化的三个属性,Looper:上面有说,Handler与创建它的线程的消息队列是绑定的,实际上Handler收发消息的三要素Handler、MessageQueue、Looper,是缺一不可的,线程必须有Looper,才能从MessageQueue里把消息取出来,所以一定要创建Looper才能使用Handler,刚刚的例子我们并没有看到Looper创建,而我们之所以能够在Activity里直接使用Handler是因为主线程,也叫UI线程、ActivityThread被创建的时候会初始化一个Looper。所以我们能够在UI线程里直接使用Looper(因为更新UI使用的多,而且Android整个窗口的UI更新都是用的Handler机制)。Callback:接口,回调用来写收到消息后处理消息的代码。async:传给消息的一个boolean型变量,是否需要同步。
直接来看我们最熟悉的sendMessage的方法:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
最普通的发送消息,其内部使用了延时发送,延时设为0. 这是Android里面常用的模式,这样做的好处是当你不需要传参数的时候可以直接使用无参的,使用方便,而且内部减少
了代码量,避免再重复写一个发送消息的实现。
/** * 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); }
发送一个空消息只需要传递一个信号,用消息类型就足够携带我们要传递的信息。同样调用了它的延时发送方法,延时为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); }
我们可以看到,系统同样是通过发送Message对象来实现的发送空消息。这个对象值承载了我们设置的What信息。它最终调的也是 sendMessageDelayed(msg, delayMillis);延时发送一个消息。这里用了 Message.obtain();这是一种单例模式,避免每发送一个消息就new出一个对象,如果这样内存占用会很高,而且没有必要。在讲Message的时候会讲到。
/** * 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> * Time spent in deep sleep will add an additional delay to execution. * 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); }
可以看到这个才是真正的发送了一条消息的方法,上面的所有发送消息的方法最终都是要调这个方法。在发送消息的最后又调用了这个enqueueMessage(queue, msg, uptimeMillis);从名字看,我们大概可以猜到,这里应该是把消息和消息确切的发送时间,塞到消息队列里面。稍有开发经验的都知道,把消息塞给队列这种事理论上应该交个MessageQueue来提供一个方法比较合理,Android为什么这么搞呢?我们带着这个疑问来看Handler提供的这个方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
这里可以看到这里这个方法并没有做什么事,只是给Message的target属性赋值,表示这个消息的接收者是自己,这不就是Handler自己发消息给自己的判断标识么。。。mAsynchronous这个值是表示是否是同步的,是在Handler构造方法里传进来的。最后又把这个属性塞给了Message,让Message自己处理是否同步的问题。后面调了MessageQueue的enqueueMessage方法,到这里Handler消息的发送过程也就结束了。(多说一句,这里传的时间,其实是用来排序的,Message里面有一个long型的属性when,就是Message的时间标签,最终这里传入的uptimeillis会赋值给msg的when属性,让MessageQueue用来根据时间对所有插入Message进行排队,构成一个单链表。存放消息和循环用。为什么用链表,数据不停的再插入删除什么的操作,所以用链表,Android真屌,我平时都注意不到这些小细节,自己上去就乱搞。在MessageQueue的源码里可以看到这个过程,这里就不再讲述了,因为我们平常也不和MessageQueue直接打交道)
/** * 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); }
和这个和上面的方法基本一样就是设置了时间为0,表示在排队时把它塞到队列的最前端。不多讲了。
postRunnable的所有方法:
/** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable 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 post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } /** * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> * Time spent in deep sleep will add an additional delay to execution. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the Runnable 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 Runnable 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 postAtTime(Runnable r, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r), uptimeMillis); } /** * Causes the Runnable r to be added to the message queue, to be run * at a specific time given by <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> * Time spent in deep sleep will add an additional delay to execution. * The runnable will be run on the thread to which this handler is attached. * * @param r The Runnable that will be executed. * @param uptimeMillis The absolute time at which the callback should run, * using the {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the Runnable 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 Runnable will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. * * @see android.os.SystemClock#uptimeMillis */ public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r, token), uptimeMillis); } /** * Causes the Runnable r to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the thread to which this handler * is attached. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> * Time spent in deep sleep will add an additional delay to execution. * * @param r The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable * will be executed. * * @return Returns true if the Runnable 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 Runnable 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 postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } /** * Posts a message to an object that implements Runnable. * Causes the Runnable r to executed on the next iteration through the * message queue. The runnable will be run on the thread to which this * handler is attached. * <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> * * @param r The Runnable that will be executed. * * @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 postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); }
这里放一块儿说,可以明显看到,post系列的所有方法,立即post,定时post,延时post方法里面都是调用的sedMessage系列的对应的方法。而一个明显的标志就是Message参数都是把Runnable对象传给getPostMessage(r)返回了一个Message对象,如果没猜错,这货又是把Runnable赋给Message里面的一个Runnable属性。来看这个方法:
private static Message getPostMessage(Runnable r, Object token) { Message m = Message.obtain(); m.obj = token; m.callback = r; return m; }
就知道,果然不出所料,Message里面有一个叫callback的Runnable属性。这样是不是很完美呢,无论你send一个Message,还是post一个Runnnable,最终都是send一个Message。然后我把Runnable放到Message的Runnnable属性里面,接到Message再拿出来,就这么简单。
好了,就先讲这么多吧,头疼,等我有空再来整理吧,先留个记号,接下来讲MessageQueue和Looper,主要讲MessageQueue如何插入一条消息,以及如何把定时的消息排队,还有Looper的死循环是运行在哪个线程里,Message里面都有哪些属性,都是干嘛的,用源码说话。好了就先这样。
相关文章推荐
- vc中使用SendMessage自定义消息函数
- C#使用SendMessage实现进程间通信的方法
- Android开发笔记之:Handler Runnable与Thread的区别详解
- android的消息处理机制(图文+源码分析)―Looper/Handler/Message
- Android消息处理机制Looper和Handler详解
- AsyncTask陷阱之:Handler,Looper与MessageQueue的详解
- Android编程开发之seekBar采用handler消息处理操作的方法
- Android中Handler消息传递机制
- Android中的Handler与多线程应用实例
- android开发教程之handler异步更新ui
- Android定时器和Handler用法实例分析
- Toast和Handler的间隔使用实例
- Android中AsyncTask与handler用法实例分析
- 深入理解Android中的Handler异步通信机制
- android开发教程之android的handler使用方法
- 详解Android中Handler的使用方法
- 详解Android中Handler的内部实现原理
- Android handler 详解(面试必问)
- Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)