您的位置:首页 > 其它

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里面都有哪些属性,都是干嘛的,用源码说话。好了就先这样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息