Handler:是一个消息分发对象,进行发送和处理消息,并且其 Runnable 对象与一个线程的 MessageQueue 关联。
作用:调度消息,将一个任务切换到某个指定的线程中去执行。
1 为什么需要 Handler?
1.1 子线程不允许访问 UI
假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。
1.2 引入 Handler
采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。
2 Handler 使用
2.1 方式一: post(Runnable)
创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作
创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。
new Thread(new Runnable() {
@Override
public void run() {
/**
耗时操作
*/
handler.post(new Runnable() {
@Override
public void run() {
/**
更新UI
*/
}
});
}
}).start();
2.2 方式二: sendMessage(Message)
创建一个工作线程,继承 Thread,重新 run 方法,处理耗时操作
创建一个 Message 对象,设置 what 标志及数据
通过 sendMessage 进行投递消息
创建一个handler,重写 handleMessage 方法,根据 msg.what 信息判断,接收对应的信息,再在这里更新 UI。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) { //判断标志位
case 1:
/**
获取数据,更新UI
*/
break;
}
}
};
public class WorkThread extends Thread {
@Override
public void run() {
super.run();
/**
耗时操作
*/
//从全局池中返回一个message实例,避免多次创建message(如new Message)
Message msg =Message.obtain();
msg.obj = data;
msg.what=1; //标志消息的标志
handler.sendMessage(msg);
}
}
new WorkThread().start();
3 Handler 存在的问题
3.1 内存方面
Handler 被作为 Activity 引用,如果为非静态内部类,则会引用外部类对象。当 Activity finish 时,Handler可能并未执行完,从而引起 Activity 的内存泄漏。故而在所有调用 Handler 的地方,都用静态内部类。
3.2 异常方面
当 Activity finish 时,在 onDestroy 方法中释放了一些资源。此时 Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起空指针的异常。
3.3 避免
如果是使用 handlerMessage,则在方法中加try catch。
如果是用 post 方法,则在Runnable方法中加try catch。
4 Handler 的改进
内存方面:使用静态内部类创建 handler 对象,且对 Activity 持有弱引用
异常方面:不加 try catch,而是在 onDestory 中把消息队列 MessageQueue 中的消息给 remove 掉。
则使用如下方式创建 handler 对象:
/**
* 为避免handler造成的内存泄漏
* 1、使用静态的handler,对外部类不保持对象的引用
* 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用
*/
private static class MyHandler extends Handler {
private final WeakReference mActivityReference;
MyHandler(Activity activity) {
this.mActivityReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity
switch (msg.what) { //获取消息,更新UI
case 1:
byte[] data = (byte[]) msg.obj;
activity.threadIv.setImageBitmap(activity.getBitmap(data));
break;
}
}
}
并在 onDesotry 中销毁:
@Override
protected void onDestroy() {
super.onDestroy();
//避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列
handler.removeCallbacks(postRunnable); //清除runnable对应的message
//handler.removeMessage(what) 清除what对应的message
}
5 Handler 通信机制
image
创建Handler,并采用当前线程的Looper创建消息循环系统;
Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;
Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。
6 Handler 如何关联 Looper、MessageQueue
6.1 Handler 发送消息
上一段很熟悉的代码:
Message msg =Message.obtain();
msg.obj = data;
msg.what=1; //标志消息的标志
handler.sendMessage(msg);
从sendMessageQueue开始追踪,函数调用关系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,携带者传来的message与Handler的mQueue一起通过enqueueMessage进入队列了。
对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。
6.2 消息入队列
在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在MessageQueue中,由Message的消息链表进行入队列
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
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. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
6.3 Looper 处理消息
再说处理消息之前,先看Looper是如何构建与获取的:
构造Looper时,构建消息循环队列,并获取当前线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:
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));
}
这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?
static final ThreadLocal sThreadLocal = new ThreadLocal();
它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。
TheadLocal 如何保存与获取Looper?
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
public T get() {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
在 set 中都是通过 values.put 保存当前线程的 Looper 实例,通过 values.getAfterMiss(this)获取,其中put和getAfterMiss都有key和value,都是由Value对象的table数组保存的,那么在table数组里怎么存的呢?
table[index] = key.reference;
table[index + 1] = value;
很显然在数组中,前一个保存着ThreadLocal对象引用的索引,后一个存储传入的Looper实例。
接下来看Looper在loop中如何处理消息
在loop中,一个循环,通过next取出MessageQueue中的消息,若取出的消息为null,则结束循环,返回。设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。
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();
}
}
6.4 handler处理消息
Looper把消息回调到handler的dispatchMessage中进行消息处理:
若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。
若handler的mCallback不为空,则交由通过callback创建handler方式去处理。
否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
7 使用中注意事项
下面我们看如何把一个消息从一个线程发送到另一个线程。
//在主线程创建一个Handler对象。
//重写Handler的handleMessage方法,这个就是接收并处理消息的方法。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//msg就是子线程发送过来的消息。
}
};
//开启一个子线程
new Thread(new Runnable() {
@Override
public void run() {
//在子线程发送一个消息。
Message msg = new Message();
handler.sendMessage(msg);
}
}).start();
上面就是一个简单的Handler使用例子。我们在主线程创建一个Handler,他的handleMessage()方法是运行在主线程的,当我们在子线程发送一个消息的时候,handleMessage()会接收到消息,这样我们就把消息由子线程发送到了主线程。上面的代码中,Handler发送的消息是一个空消息,什么数据也没有,如果我们只是单纯的发送一个空消息,可以使用Handler自己的发送空消息的方法handler.sendEmptyMessage(what);
这两种发送空消息的效果是一样的。至于这里的what是什么我们后面会说到。Handler也可以发送带有数据的消息。Message对象有一个Object类型的obj属性,就是用来携带消息数据的。我们只需要把要发送的数据赋值给obj就可以了,然后在处理消息的时候再把数据取出来。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.i(TAG,"Handler 发送过来的消息是:" + msg.obj);
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.obj = "我是消息数据";
handler.sendMessage(msg);
}
}).start();
上面的代码中,msg携带一个字符串数据:"我是消息数据",在handleMessage()方法中把这个数据取出来。除了obj以外,Message还有两个int类型的属性:arg1、arg2可以用来发送一些简单的数据。
现在我们看到的所有的消息都是在Handler的handleMessage()方法中处理,如果我要发送很多个消息,每个消息的数据都不一样,消息的处理逻辑也不一样,那么在handleMessage()方法中如何去判断哪个消息是哪个呢?这时Message的what属性就派上用场了,what属性就是上面发送空消息时我们看到的what,它是一int类型,它是用来标识消息的。
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i(TAG, "第一个消息是:" + msg.obj);
break;
case 2:
Log.i(TAG, "第二个消息是:" + msg.obj);
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg1 = new Message();
msg1.obj = "第一个消息";
msg1.what = 1;
handler.sendMessage(msg1);
Message msg2 = new Message();
msg2.obj = "第二个消息";
msg2.what = 2;
handler.sendMessage(msg2);
}
}).start();
上面我们在子线程中发送了两个消息,并且给两个消息设置了它的what,在处理消息的时候就可以通过msg的what来判断是哪个消息了。
除了用sendMessage发送Message消息以外,Handler还可以post一个Runnable。
new Thread(new Runnable() {
@Override
public void run() {
//在子线程post一个Runnable对象
handler.post(new Runnable() {
@Override
public void run() {
//这里是消息处理的方法
//这里运行在主线程。
}
});
}
}).start();
其实post()方法和sendMessage()方法的逻辑是一样的,post()方法中的Runnable会被封装成Message,然后发送出去。下面看它的源码:
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;
}
从源码中我们看到,Runnable会被封装成Message,然后还是用sendMessage的方式发送出去。而且封装的逻辑也很简单,直接把Runnable赋值给Message的callback就可以了。在上一篇文章中我介绍了消息处理的三个方法,Message自己的callback优先级是最高的,所以这个消息是由自己的callback也就是Runnable的run()方法处理的,而不再是Handler的handleMessage()方法。
前面我们所举的例子中,都是消息的处理是在主线程的。其实不然,消息的处理事实上是运行在负责管理消息队列(MessageQueue)的Looper所在的线程的,而不一定是主线程。
如果我们创建一个Handler对象而没有给它指定它的Looper,那么它默认会使用当前线程的Looper。前面我们所举的例子都是在主线程直接创建Handler对象的,所以它的Looper就是主线程的Looper,它的消息自然也就是在主线程处理了。那么我们也可以在子线程使用Handler,下面可一个例子:
//声明Handler;
Handler handler;
new Thread(new Runnable() {
@Override
public void run() {
//创建当前线程的Looper
Looper.prepare();
//在子线程创建handler对象
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//这里是消息处理,它是运行在子线程的
}
};
//开启Looper的消息轮询
Looper.loop();
}
}).start();
mBanner.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在主线程发送一个消息到子线程
Message msg = new Message();
handler.sendMessage(msg);
}
});
在上面的例子中,我们在子线程创建handler对象,handler的Looper就是子线程的Looper,所以消息的处理也就是在子线程处理的。这就是在子线程使用Handler的方式。在这里有几个需要注意的点:
在子线程使用Handler前一定要先为子线程创建Looper,创建的方式是直接调用Looper.prepare()方法。前面我们说过创建Handler对象时如果没有给它指定Looper,那么它默认会使用当前线程的Looper,而子线程默认是没有Looper的,所以使用前一定要先创建Looper。
在同一个线程里,Looper.prepare()方法不能被调用两次。因为同一个线程里,最多只能有一个Looper对象。
只有调用了Looper.loop()方法,Handler机制才能正常工作。 Looper负责管理MessageQueue,它的loop()方法负责从MessageQueue里取出消息并交给Handler处理,所以如果没有调用Looper.loop()方法,消息就不会被取出和处理。
Looper.loop()方法一定要在调用了Looper.prepare()方法之后调用。那是因为如果当前线程还没有Looper,是不能调用Looper.loop()方法开启消息轮询的,否则会报错。
不要在主线程调用Looper.prepare()方法。这是因为在Android系统创建主线程的时候就已经调用了Looper.prepare()方法和Looper.loop()方法,这也是为什么我们在主线程使用Handler时不需要调用这两个方法的原因。
当我们在子线程使用Handler时,如果Handler不再需要发送和处理消息,那么一定要退出子线程的消息轮询。因为Looper.loop()方法里是一个死循环,如果我们不主动结束它,那么它就会一直运行,子线程也会一直运行而不会结束。退出消息轮询的方法是:
Looper.myLooper().quit();
Looper.myLooper().quitSafely();
上面的例子都是用线程自己的Looper来创建Handler,我们也可以用指定的Looper来创建Handler。
new Thread(new Runnable() {
@Override
public void run() {
//获取主线程的Looper
Looper looper = Looper.getMainLooper();
//用主线程的Looper创建Handler
handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
//这里是运行在主线程的
}
};
}
}).start();
上面的例子中,我们虽然是在子线创建Handler,但因为用的是主线程的Looper,所以消息的处理是在主线程的,这跟在主线程创建Handler是一样的。因为这里并没有使用到子线程的Looper,所以不要调用Looper.prepare()和Looper.loop()方法。
以上我们所说的是Handler切换线程的使用。Handler除了提供post()方法和sendMessage()方法以外,还提供了一系列的发送消息的方法。比如延时发送消息和定时发送消息:
//延时发送消息
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean postDelayed(Runnable r, long delayMillis);
//定时发送消息
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
通过这些方法,可以实现延时执行方法和定时执行方法的功能。如下面的例子:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "延时1000毫秒打印");
}
},1000);
上面的例子并没有涉及到线程的切换,只是利用了Handler延时发送消息的功能达到延时打印。所以Handler的使用不仅仅是切换线程。更多的方法使用就不一一举例了。
小知识点:
使用Message.obtain()来获取一个消息。前面我们的例子中获取一个消息都是用new的方式直接创建,我这样做只是为了方便大家的理解而已。在使用中不推荐用这种方式来获取一个消息,而是使用Message.obtain()方法。
Message msg = Message.obtain();Android会为Message提供一个缓存池,把使用过的消息缓存起来,方便下次使用。我们用Message.obtain()方法获取一个消息时,会先从缓存池获取,如果缓存池没有消息,才会去创建消息。这样做可以优化内存。
同一个Message不要发送两次。如下面的代码是有问题的:
//同一个Message发送了两次
Message msg = Message.obtain();
handler.sendMessage(msg);
handler.sendMessage(msg);
这是因为所以的消息都是发送到MessageQueue存放,然后等待处理的。如果一个Message对象在MessageQueue里,再次把它存到MessageQueue时就会报错。
Android已经提供了很多实现了Handler的类和方法,方便我们使用。如Activity类的runOnUiThread()方法,View的post()方法,HandlerThread类等,关于这些知识,大家可以查阅相关资料,这里就不做讲解了,因为他们的实现其实跟我们前面说的是一样的。
参考: