您的位置:首页 > 其它

Handler与HandlerThread

2014-12-18 17:07 190 查看
概述:

在线程之间通信时,通常会使用到Handler。与之相关联的类有:Message,Looper,MessageQueue。简单讲它们的作用分别为:

Handler:发送消息并且处理消息的对象。

Message:Handler接收和处理的对象。

Looper:每一个线程只能拥有一个Looper对象。它的loop()方法会从MessageQueue中读取消息,并且将读到的消息发送给该消息的Handler进行处理。

MessageQueue:消息队列,它采用先进先出的方式管理消息(Message)。在Looper对象初始化时会创建MessageQueue的实例。Looper的构造方法如下:

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

从这里可以看出,在Looper初始化时,会创建一个与之相关联的MessageQueue对象。

ThreadLocal

在子线程中使用Handler时,需要先调用Looper.prepare(),主要是为当前线程绑定一个Looper对象。主要涉及set()与get()方法。如下:

public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);//获取当前线程的localValues属性。
if (values == null) {
values = initializeValues(currentThread);//return current.localValues = new Values();
}
//经上面两步之后,当前线程的localValues肯定不为null,并且values也为当前线程的localValues
values.put(this, value);//将set的参数设置到localValues属性中。
}

从中可以看出,一次prepare()只是为当前线程绑定了一个Looper对象。而一个Looper对象又关联的有一个MessageQueue,所以每一个MessageQueue只属性一个线程。所以每一次在新线程使用Looper时必须先调用prepare()。

Looper

在Looper中,有两个方法最重要:prepare()用来创建Looper对象,并与线程关联的;loop()用于从与Looper对象关联的MessageQueue中取出消息(Message)。

创建Looper对象,调用它的prepare()即可。代码如下:

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));
}

首先说一下sThreadLocal,它是一个ThreadLocal<Looper>对象。而ThreadLocal是一个很特殊的全局变量,它的全局性只局限于当前的线程,外界所有的线程(包括处于同一个进程中的)都无法访问到它。

从代码中可以看出,首先从sThreadLocal中取值,如果有值说明当前线程已经被关联过Looper对象,此时就直接抛异常;如果没有,那就新new一个Looper对象,并且设置到sThreadLocal中。通过此种方法就可以保证一个线程只能拥有一个Looper对象。prepare()也只做了一件事:在允许的情况下,为当前线程关联一个Looper对象。

又由于一个线程只能关联一个Looper对象,所以一个线程prepare()只能被调用一次。

loop()方法使用一个死循环来不断取出存储在MessageQueue中的消息,并将消息交给该消息对应的handler进行处理。大体代码如下:

/**
* 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.");
}
//取出Looper关联的MessageQueue对象
final MessageQueue queue = me.mQueue;
…
for (;;) {
//从MessageQueue中取出下一条消息(Message),该方法是阻塞的
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
…
//将取到的消息进行分发
msg.target.dispatchMessage(msg);
…
msg.recycle();//回收该消息
}
}
从中可以看出,loop()用for(;;)进行了一个无限循环,也就是说:这个loop()方法会进行不断地读取消息,直到取到或者MessageQueue被放弃。
当Looper取到消息时,会调用msg.target.dispatchMessage()(就是发送msg的handler中的dispatchMessage())。由于Looper.loop()是在Looper对象所处的线程中执行的,所以handler.dispatchMessage()的运行线程与Looper对象所属的线程是同一个,而不一定与handler所处的是同一个线程。

如果想获取当前线程中的Looper对象,可以调用Looper.myLooper(),它会直接返回sThreadLocal.get();。

处理完msg后,会调用msg.recycle()将Message对象进行回收处理。

因此,Looper#loop()方法中一个Message会完成自己的整个生命周期的后半部分:获取,处理以及回收。

prepare()与prepareMainLooper()

prepare()为当前线程添加Looper对象,而后者是是为UI线程准备Looper对象的。如下:

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));
}

public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

从上可以看出,两者调用了相同的方法,只是一个参数为false,一个参数为true。这个参数最终会赋值到MessageQueue#mQuitAllowed中,而它代表着这个MessageQueue能否被打断,简单点就是能否调用Looper#quit()方法。

从上面代码看出,UI线程中的是无法打断Message循环的,而自己创建的线程却可以。并且prepareMainLooper()是在ActivityThread#main()中调用的,是先于我们所有的线程调用的,调用完毕之后sMainLooper有值,再次调用时就会报错,这也防止了自己在线程中调用prepareMainLooper。

Message

对于一个Message实例来说,包含好几个实例变量。

1,what 用户定义的int型消息代码,用于区别不同的消息

2,obj 随消息传递的对象,用于传递数据

3,target处理消息的handler。在上面的loop()代码中,就是调用msg.target.dispatchMessage()将消息传递回handler中。

4,next。记录链表中下一个元素。在使用Message时,系统建议使用obtain(),而不是直接new Message()。这是因为在系统中维护了一个使用过Message的链表结构,每一次obtain时都会从这个链表中获取,防止了重复new Message造成各种内存开销。

5,sPool 链表结构的表头。

6,flags当前消息的状态。

对于Message实例的获取,通常通过handler.obtainMessage()及重载方法完成。在obtainMessage()的源码中可以发现,它里面只是调用了Message.obtain(this),这里的this指的就是调用obtainMessage()的Handler对象。下面看一下obtain()的源码:

public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;

return m;
}

这里面直接将Message的target属性设置成了当前的handler。这样在Looper.loop()进行消息分发时就可以找到对象的Handler对象了。再看obtain()方法:

public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
取的时候先从链表的表头开始(sPool代表的就是表头),然后将表头移到第二个元素上。然后将取得元素的next置为null,并将sPoolSize数量减1。当链表中没有缓存的Message对象时,会new一个Message对象。
在上面的Looper.loop()中,调用Message#recycle()方法进行回收,它经过一个判断之后会调用recycleUnchecked()方法。代码如下:

void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;

synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
该方法中首先将一个Message中的属性都初始化。然后将该Message对象设置到链表的表头。
一个recycle()一个obtain(),两者方法结合,就完成了Message的复用。可以简单理解为Message中维护了一个Message对象池(使用链表缓存),而这个池的最大容量是MAX_POOL_SIZE(50)。使用obtain()方法获取Message对象时,首先会从链表中取Message对象,如果链表中为空,就new一个新建Message并返回。而Message对象在使用之后会添加到链表的,供下次通过obtain()获取时复用。

其实在Message这里使用的是享元模式,对于享元模式最大问题在于线程安全,所以在obtain()中加了同步。这保存了一个Message只会出现在一个线程中,从而避免了线程问题。

Handler

它的作用只有两个:发送Message和处理Message。程序使用Handler发送消息时,被发送的消息必须被送到指定的MessageQueue中。也就是说:如果希望Handler能够正常工作,必须在当前线程中有一个MessageQueue,否则消息就没地方存储,而MessageQueue是由Looper在构造时创建的。因此,为保证handler正常工作,它所在的线程必须有一个Looper对象。这里可以分成两种情况:

第一种:当在UI线程中使用Handler。由于主线程已经创建了Looper,所以可以直接使用。

第二种:当在非UI线程中时,必须自己创建一个Looper对象,并且将该Looper绑定到当前线程中(调用Looper.prepare()),并启动它(Looper.loop())。

dispatchMessage()

通过上面Message与Looper的分析,发现Message最终会传递到Handler的dispatchMessage()中,它的代码如下:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
从这一段代码可以看出,我们一般只需要重写Handler.handleMessage()就会收到在别处发的消息。

构造方法

下面再看一下Handler构造方法,在构造方法中有两个是最重要的:获取当前Handler关联的Looper以及存储handler发送消息的MessageQueue。虽然handler的构造方法有多个,但是最终会执行下面两个中的一个:

//不传入Looper对象时
public Handler(Callback callback, boolean async){
…
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
…
}
//传入Looper对象时
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

当不传入Looper对象时,与handler关联的Looper便是当前线程中存储的Looper实例。因为,如果当前线程没有Looper实例时,便会抛出异常。在UI线程中,系统已经为该线程关联了一个Looper对象,故可以直接使用new Handler();但在非UI线程中,我们需要在实例化handler之前调用Looper.prepare()为当前线程关联一个Looper对象。

当传入了Looper对象时,Handler关联的便是传入的Looper对象。此时handler发送的消息便会存储到该Looper对象的MessageQueue中。然后Looper通过loop()不断地从自己的MessageQueue中取出消息,再进行分发。

记传入的Looper对象所在的线程为TA,Handler实例所处的线程为TB。当TA与TB不一样时,handler一样可以将消息发往looper的MessageQueue中。由于Looper.loop()运行在TA线程中,因此loop()方法中调用的msg.target.dispatchMessage()一样是运行在TA线程中,而不是运行在TB中。再结合dispatchMessage()的源码可知,此时Handler.handleMessage()也是在TA中的。

looper取到的msg始终是与自己关联的MessageQueue中的,所以MessageQueue处于哪个线程中,就意味着它里面的msg会被哪个线程中的looper.loop()取到,进而决定了Hanlder.handleMessage()运行在哪个线程中。但是由于MessageQueue经常由Looper.myQueue()获取,所以Looper对象也就决定了Handler.handleMessage()运行的线程.

上面TB中hanlder发送的消息会存储到TA中的MessageQueue中,TA中looper会不断从TA中MessageQueuek 取消息,某一刻会取到TB中handler发送的msg,从而会在TA中调用TB中Handler.handleMessage()。

因此,可以总结一句:与handler关联的Looper对象决定了Handler.handleMessage()执行时所处的线程,或者更准确地说,handler发送的消息存储到的MessageQueue决定了Handler.handleMessage()执行时所处的线程。当Looper实例处于UI线程中,不管handler位于哪个线程,都可以在Handler.handleMessage()中更新界面,因为这个方法是在UI线程中执行的。示例如下:

tv = (TextView) findViewById(R.id.tv);
new Thread() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Looper.prepare();
System.out.println("线程:" + Thread.currentThread().getName());
//new handler时传入的Looper是UI线程中的
Handler subHandler = new Handler(getMainLooper()) {
public void handleMessage(Message msg) {
System.out.println("线程:"
+ Thread.currentThread().getName());
tv.setText("我是在run中运行的");
};
};
subHandler.sendEmptyMessage(0);
Looper.loop();
};
}.start();
在上述代码中,subHandler是处于子线程中的,但是handleMessage()是执行在UI线程中的,所以代码不会报错,而且也会更新成功。但是,如果我们在Handler的实例化时不传入getMainLooper(),则会崩掉,异常就是不能在子线程中更改UI。至于为什么要在前面加上Thread.sleep(2000),参看碎雨(四)中的"非UI线程更新UI"。

发送消息

在Handler中常用sendEmptyMessage和sendMessage发送消息,两者最终会走到sendMessageAtTime(Message, long)中。sendMessageAtTime()的代码为:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//首先获取MessageQueue
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);
}
这里的mQueue都是通过Looper.mQueue属性获得的。 也就是说,handler发送的消息最终会存储到与之相关联的Looper对象的MessageQueue中,而Looper.loop()也是从与自己相关联的MessageQueue中获取的Message对象。从而保证了handler发送的消息会被正确的取出来。
其中enqueueMessage()的代码为:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//在这里将Message.target的属性给设置上了
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

在发送消息时,除了send*系列方法,还有一个post()。源码如下:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
从中可以看出,post()也是将参数Runnable封装成Message然后发送出去。此时该Message的callback变量就是Runnable对象。
再结合Handler.dispatchMessage()可以看出,收到该消息后只会运行Message.callback,并不会执行Handler类中的handleMessage()等。

private static void handleCallback(Message message) {
message.callback.run();
}
在该方法中,终于执行了post()中传入的Runnable对象的run()。
因此,当通过Handler.post()运行时,Message对象的callback属性不为空,就会执行传入post中Runnable.run()方法。如果使用Handler#sendMessage(),Message对象的callback为空,就会执行到Handler#handleMessage()方法。

MessageQueue

在上面的handler.sendMessage()代码中,最终会调用到MessageQueue.enqueueMessage(),大体代码如下:

boolean enqueueMessage(Message msg, long when) {
……
synchronized (this) {
……
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
……// Inserted within the middle of the queue.
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
……
}
return true;
}
从中可以发现,它是通过Message.next将整个Message串成一连Message链,每一个Message都通过next属性记住它的下一个Message。这就是MessageQueue存储Message的过程。

总结

handler把消息发送到与之关联的looper中的MessageQueue中。Looper.loop()会不断地从MessageQueue中取新消息,并将取到的消息传递到Handler.dispatchMessage()中,从而将消息又传回handler中。

message能正确地返回到发送它的handler对象中,是因为message.target变量记录下了handler对象。

由于looper对象决定了handleMessage()调用的线程,所以Handler可以用于线程之间的通信。比如在子线程中访问网络,然后通过handler将结果返回到UI线程,进而更新UI。

一个线程最多只能关联一个Looper对象,一个Looper对象对应一个MessageQueue对象,而MessageQueue中可以存储多条不同的Message,每一个Message最多只能指定一个Handler进行处理。因此,线程与Handler是一对多的关系。

HandlerThread

当在子线程中使用Handler时,需要为该线程关联一个Looper对象。此时可以使用HandlerThread类,它本身就是一个Thread的子类,它的run()方法如下:

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//为该线程关联一个Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();//启动关联的looper对象
mTid = -1;
}

从中可以看出HandlerThread已经调用过了Looper.prepare()和Looper.loop()。因此,在调用start()之后可以直接使用Handler。

在调用Looper.loop()之前,调用了onLooperPrepared(),这是一个空方法,一般会在该方法中添加执行一些设置。比如,初始化Handler之类的。

由于onLooperPrepare()是在run()方法中调用的,所以onLooperPrepared()运行在子线程中,因此可以在里面进行一些访问网络之类的操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: