1、什么是Handler
handler是android给我们提供用来更新UI的一套机制,也是一套消息处理,我们可以发送消息,也可以通过它处理消息。
2、android为什么要设计只能通过Handler机制更新UI呢
最根本的目的就是解决多线程并发的问题。
假设如果再一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样子的问题?
更新界面混乱
如果对更新UI的操作都进行加锁处理的话又会产生什么样子的问题?
性能下降
处于对于以上目的的问题的考虑,android给我们提供了一套更新UI的机制,我们只需要遵循这样的机制就可以了。而根本不用去关心多线程的问题,所有的更新UI的操作,都是在主线的消息队列当中去轮询处理。
3、hanlder怎么用
先看看如果在子线程中更新UI会抛出什么样的错误
new Thread(){
@Override
public void run() {
SystemClock.sleep(1000);
textView.setText("Thread中更新UI");
}
}.start();
抛出的异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Handler的使用方法有好几种:
post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long),
sendEmptyMessage(int),sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long)
我们介绍其中一种使用方法:
先实例化Handler
private Handler handler= new Handler();
在我们的子线程中更新UI
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("Thread中更新");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
4、Handler的原理
- Handler封装了消息的发送(主要包括消息发送给谁)
- Looper
1.内部包含一个消息队列也就是MessageQueue,所有的Handler发送的消息都走向这个消息队列。
2.Looper.Looper方法,就是一个死循环,不断从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞 - MessageQueue
就是一个消息队列,可以添加消息,并处理消息 - Handler也很简单,内部会跟Looper进行关联,也就是说在Handler的内部可以找到Looper,找到了Looper也就找到了MessageQueue,在Handler中发送消息,其实就是向MessageQueue队列中发送消息
总结:handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给handler自己,MessageQueue就是一个存储消息的容器。
那么他的内部原理是什么呢?我们通过源码再来更深入的了解:
不论是handler.sendMessage(msg);或者是sendEmptyMessage(),post()方法都是调用的sendMessageAtTime(Message msg, long uptimeMillis)方法
那么在这个方法里都有什么呢?
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);
}
send函数其实就是往消息队列里面插入一条消息,说道MessageQueue消息队列就得再来看看Looper了。
for (;;) {
Message msg = queue.next(); // might block
//后面还有一大堆的东西,上面这一句和下面这一句最重要
msg.target.dispatchMessage(msg);
}
Looper的loop函数又会调用MessageQueue的next函数去获取消息,最后在交给Handler的dispatchMessage函数处理,这样子我们就把Handler, MessageQueue, Looper三者就有了一定的关系。
再来看看Handler的dispatchMessage都做了什么:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里有两个回调,先看第一个handleCallback
private static void handleCallback(Message message) {
message.callback.run();
}
这个callback是一个Runnable类,到这里我们应该就明白了最开始我们提到的使用Handler处理耗时操作和UI操作的原理了。
那么当msg.callback==null的情况呢,这个mCallback又是什么?
查看源码其实是Handler的构造函数
public Handler(Callback callback, boolean async) {
...
}
最后再来看看Callback是是什么:
public interface Callback {
public boolean handleMessage(Message msg);
}
发现Callback是一个接口,再回想我们于是我们应该就会想起我们在创建Handler的时候有时候会写这样子的代码:
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
是的,这个时候dispatchMessage就会来执行Handler的handleMessage(msg)方法来完成主线程UI的更新。
最后再来一段总结:handler.sendMessage函数最终还是转换成了send系列函数往MessageQueue里面插入了一条消息队列,然后主线程已经为我们创建好的Looper对象在loop函数中调用了MessageQueue的next()方法来读取到这条消息,再调用Handler的dispatchMessage函数来处理消息,在dispatchMessage里面会去执行postDelayed中第一个参数runnbale开启的子线程。如果没有开启子线程Runnable则会去执行Handler的handleMessage()方法。
5、四种子线程更新UI的几种方法
- 1.第一种最基本的方法即在子线程中发送消息sendMessage,类似的sendEmptyMessage(int),sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long)都属于同一类。
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
Message msg = handler.obtainMessage();
msg.arg1 = 1;
handler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
然后在handler中处理
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
textView.setText(msg.arg1+"");
}
};
- 2.直接在子线程中调用handler.post()方法:
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("Thread中更新");
}
});
- 3.控件也有post方法
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("Thread中更新");
}
});
- 4.在子线程使用 runOnUiThread()方法
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("Thread中更新");
}
});
6、非UI线程真的不能更新UI吗?
其实是可以的,但是强烈不推荐
new Thread(){
@Override
public void run() {
try {
Thread.sleep(10);
textView.setText("Thread中更新");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
在我们的线程中,睡眠时间极短或者没有睡眠的时间的时候。
查看源码是因为onCreate()方法先于onResume()方法,所以我们在子线程更新UI的时候,还没走到checkThread()方法。它就并不知道我们在子线程更新了UI,这种情况下时可以更新的。
再次重申,不推荐,只是告诉大家一种特例情况。