一.背景
总结了很多大神对handler机制(感谢各位大神),很早以前就写了,最近回想居然想不起来,于是在硬盘的某个阴暗角落把他找出来了,方便以后查阅,写成博客,方便自己,也方便大家
二.概述
Android使用消息机制实现线程间的通信,线程通过Looper建立自己的消息循环,MessageQueue是FIFO的消息队列,Looper负责从MessageQueue中取出消息,并且分发到消息指定目标Handler对象。Handler对象绑定到线程的局部变量Looper,封装了发送消息和处理消息的接口
三.例子
这个例子实现点击按钮之后从主线程发送消息"hello"到另外一个名为” CustomThread”的线程。
-
class CustomThread extends Thread {
-
@Override
-
public void run() {
//建立消息循环的步骤
//1.初始化Looper(下面有解释具体过程)
Looper.prepare();//1、初始化Looper
//2.绑定handler到CustonThread实例的Looper(下面有解释具体过程)
mHandler = new Handler(){
//3.定义处理消息的方法
-
public void handleMessage (Message msg) {//3、定义处理消息的方法
-
switch(msg.what) {
-
case MSG_HELLO:
-
Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);
-
}
-
}
-
};
//4.启动消息循环(下面有解释具体过程)
-
Looper.loop();
-
}
-
}
我们看到,为一个线程建立消息循环有四个步骤:
1、 初始化Looper
//Looper.java
-
/** Initialize the current thread as a looper.
-
* This gives you a chance to create handlers that then reference
-
* this looper, before actually starting the loop. Be sure to call
-
* {@link #loop()} after calling this method, and end it by calling
-
* {@link #quit()}.
-
*/
-
public static void prepare() {
-
prepare(true);
-
}
-
private static void prepare(boolean quitAllowed) {
-
if (sThreadLocal.get() != null) {
-
throw new RuntimeException("Only one Looper may be created per thread");
-
}
-
sThreadLocal.set(new Looper(quitAllowed));
-
}
2、 绑定handler到CustomThread实例的Looper对象
一个线程在调用Looper的静态方法prepare()时,这个线程会新建一个Looper对象,并放入到线程的局部变量中,而且这个变量是不和其他线程共享的(关于ThreadLocal的介绍)。
下面我们看看Looper()这个构造函数:
-
final MessageQueue mQueue;
-
private Looper(boolean quitAllowed) {
-
mQueue = new MessageQueue(quitAllowed);
-
mRun = true;
-
mThread = Thread.currentThread();
-
}
可以看到在Looper的构造函数中,创建了一个消息队列对象mQueue,此时,调用Looper. prepare()的线程就建立起一个消息循环的对象(此时还没开始进行消息循环,需要调用loop才会开始循环)。
//handler.java
-
final MessageQueue mQueue;
-
final Looper mLooper;
-
final Callback mCallback;
-
public Handler() {
-
this(null, false);
-
}
然后就会调用这个
-
public Handler(Callback callback, boolean async) {
-
if (FIND_POTENTIAL_LEAKS) {
-
final Class<? extends Handler> klass = getClass();<pre name="code" class="java">
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; }
-
public static @Nullable Looper myLooper() {
-
return sThreadLocal.get();
-
}
Handler通过mLooper = Looper.myLooper();绑定到线程的局部变量mLooper上去,同时Handler通过mQueue =mLooper.mQueue;获得线程的消息队列。此时,Handler就绑定到创建此Handler对象的线程的消息队列上了
3、 定义处理消息的方法
子类需要覆盖这个方法,实现接受到消息后的处理方法。
4、 启动消息循环
所有准备工作都准备好了,是时候启动消息循环了!Looper的静态方法loop()实现了消息循环。
-
/**
-
* 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.recycleUnchecked();
-
}
-
}
while(true)体现了消息循环中的“循环“,Looper会在循环体中调用queue.next()获取消息队列中需要处理的下一条消息。当msg != null且msg.target != null时,调用msg.target.dispatchMessage(msg);分发消息,当分发完成后,调用msg.recycle();回收消息。
msg.target是一个handler对象,表示需要处理这个消息的handler对象。
//handler.java
-
public final boolean sendMessage(Message msg)
-
{
-
return sendMessageDelayed(msg, 0);
-
}
-
public final boolean sendMessageDelayed(Message msg, long delayMillis)
-
{
-
if (delayMillis < 0) {
-
delayMillis = 0;
-
}
-
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
-
}
-
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);
-
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
-
if (mAsynchronous) {
-
msg.setAsynchronous(true);
-
}
-
return queue.enqueueMessage(msg, uptimeMillis);
-
}
然后就把当前调用的handler传给msg.tartget
下面是分发消息
-
public void dispatchMessage(Message msg) {
-
if (msg.callback != null) {
-
handleCallback(msg);
-
} else {
-
if (mCallback != null) {
-
if (mCallback.handleMessage(msg)) {
-
return;
-
}
-
}
-
handleMessage(msg);
-
}
-
}
可见,当msg.callback== null 并且mCallback == null时(msg.callback 就是Handler的post方法传递的Runable,mCallback 是 Handler handler = new Handler(callback)),这个例子是由handleMessage(msg);处理消息,上面我们说到子类覆盖这个方法可以实现消息的具体处理过程。
总结:从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。
method1:
-
private Handler handler = new Handler() {
-
@Override
-
public void handleMessage(Message msg) {
-
super.handleMessage(msg);
-
}
-
};
method2:
-
Handler handler = new Handler(new Handler.Callback() {
-
@Override
-
public boolean handleMessage(Message msg) {
-
return false;
-
}
-
});
method3:
-
new Handler().postDelayed(new Runnable() {
-
@Override
-
public void run() {
-
}
-
}, 200);
四.总结
职责
Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
同时通过sendmessage来指定msg.target(把当前handler设置为msg.target)
MessageQueue:消息队列(采用单链表的数据结构来存储信息),用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
关系
Handler,Looper和MessageQueue就是简单的三角关系。Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue。而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue。
这样说来,多个Handler都可以共享同一Looper和MessageQueue了。当然,这些Handler也就运行在同一个线程里。(looper是绑定当前线程)(但是在handler.sendmessage中已经把msg.target,设定为当前handler对象,然后放在消息队列中,然后looper.loop()中轮询消息队列,获得消息,可以调用msg.target来handlemessage)
如何更新UI
剩下的部分,我们将讨论一下Handler所处的线程及更新UI的方式。
在主线程(UI线程)里,如果创建Handler时不传入Looper对象,那么将直接使用主线程(UI线程)的Looper对象(系统已经帮我们创建了);在其它线程里,如果创建Handler时不传入Looper对象,那么,这个Handler将不能接收处理消息。在这种情况下,通用的作法是:
-
class LooperThread extends Thread {
-
public Handler mHandler;
-
public void run() {
-
Looper.prepare();
-
mHandler = new Handler() {
-
public void handleMessage(Message msg) {
-
// process incoming messages here
-
}
-
};
-
Looper.loop();
-
}
-
}
在创建Handler之前,为该线程准备好一个Looper(Looper.prepare),然后让这个Looper跑起来(Looper.loop),抽取Message,这样,Handler才能正常工作。
因此,Handler处理消息总是在创建Handler的线程里运行。而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的。
注意:
如果手动创建Looper,那么所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态,而且如果退出Looper之后,这个线程会立即终止,因此建议不需要的时候终止Looper.
(1)quit 直接退出
(2)quitsafely 只是设定了一个退出标记,然后把消息队列中的已有消息处理完毕后才能安全退出
还有Looper.loop是一个死循环,唯一跳出循环的方式就是MessageQueue的next方法返回null(只有当Looper的quit方法被调用时,才会让next方法返回null,也就是说Looper必须退出),否则loop方法就会无限循环下去,而且当没有消息的时候,next方法就会一直阻塞在那里,也就导致loop方法一直阻塞在那里。
如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程:
· Activity.runOnUiThread(Runnable)
· View.post(Runnable) (把消息放到UI线程的消息循环中)
· View.postDelayed(Runnable, long)
· Handler
其中,重点说一下的是View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。
几点小结
· Handler的处理过程运行在创建Handler的线程里
· 一个Looper对应一个MessageQueue
· 一个线程对应一个Looper
· 一个Looper可以对应多个Handler
· 不确定当前线程时,更新UI时尽量调用post方法