参考
Handler的理解、用法以及运行机制原理
android Handler机制原理 4个组成部分源码解析
Android Handler消息机制原理最全解读(持续补充中)
Handler是android提供用于更新UI的一套机制,也是消息处理机制。
Handler的主要作用有两个:
(1).在新启动的线程中发送消息
(2).在主线程中获取,处理消息。
当需要进行子线程和主线程(UI线程)通讯时,我们经常要用到Handler。
最常见的场景就是,UI在主线程初始化后,子线程做了一些耗时的数据操作,做完操作后要通知主线程拿到数据去更新UI。
Handler机制
Handler机制也可叫异步消息机制,它主要由4个部分组成:Message,Handler,MessageQueue,Looper,在上面图中我们已经有了大致印象,接下来介绍一下这4个成员:
1.Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。使用Message的arg1和arg2便可携带int数据,使用obj便可携带Object类型数据。
2.Handler
Handler顾名思义就是处理者的意思,它只要用于在子线程发送消息对象Message,在UI线程处理消息对象Message,在子线程调用sendMessage方法发送消息对象Message,而发送的消息经过一系列地辗转之后最终会被传递到Handler的handleMessage方法中,最终在handleMessage方法中消息对象Message被处理。
3.MessageQueue
MessageQueue就是消息队列的意思,它只要用于存放所有通过Handler发送过来的消息。这部分消息会一直存放于消息队列当中,等待被处理。每个线程中只会有一个MessageQueue对象,请牢记这句话。其实从字面上就可以看出,MessageQueue底层数据结构是队列,而且这个队列只存放Message对象。
4.Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当MesssageQueue中存在一条消息,Looper就会将这条消息取出,并将它传递到Handler的handleMessage()方法中。每个线程只有一个Looper对象。
了解了上述Handler机制的4个成员后,整个handler异步消息机制流程思路再理一遍:
1.首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UI线程的逻辑处理。
2.接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去。
3.之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法。
4.最后前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法,消息得到处理。
handler 的 post 方法有啥用?
1. 先看用法1之主线程中使用:
new Handler().post(new Runnable() {
@Override
public void run() {
mTextView.setText("post"); //更新UI
}
});
可以看到,new了Runnable像是开启了一个子线程,但是不然,大家可以看到这儿调用的是run方法,而不是start方法,因此并不是子线程,其实还是在主线程中(关于run和start的概念不清楚的可参考:https://www.jianshu.com/p/6b11e57cdc17),那为什么又要使用post方法呢?其实一般不这样用,也没人这样用,并没有多大意义,这就像是在主线程中给主线程sendmessage,并没有什么意义(我们知道sendmessage是子线程为了通知主线程更新UI的),主线程是可以直接更新UI的。
2 再看用法2之子线程中使用:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("post");//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
由上面总结我们知道这儿的post并不是新开启的子线程,存在的子线程只有一个,即为new的Thread,那么为什么我们在其中可以settext做更新UI的操作呢? 其实post方法post过去的是一段代码,相当于将这个Runable体放入消息队列中,那么looper拿取的即为这段代码去交给handler来处理,其实也相当于我们常用的下面这段代码:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mTextView.setText("handleMessage");//更新UI
break;
}
}
};
看起来熟悉吧,就是用这个Runnable体代替了上面这一大段代码,当然,我们的post方法就可以执行UI操作了。
平常情况下我们一个activity有好多个子线程,那么我们都会采用上面这种handleMessage(msg)方式,然后case 0:case 1:等等,但是当我们只有一个子线程时呢,用post反而比上面一大串代码轻便了不少,何乐而不为呢?
总结
1.post使用场景:当有多个子线程要通知UI更新时,还是采用handler.sendMessage发消息方便,如果只有一个子线程更新UI时则直接通过post来传递Runnable,相当于也是发一条消息。
2.handle的 post 其实并没有新创建线程。
关于new Handler()与new Handler(Looper.getMainLooper())区别
如果你不带参数的实例化:Handler handler=new Handler(); 那么这个会默认用当前线程的Looper对象。
一般而言,如果你的Handler是要用来刷新UI的,那么就需要在主线程下运行。
情况:
1 要刷新UI,handler要用到主线程的Looper对象。
那么在主线程Handler handler=new Handler() 如果在其他非主线程也要满足这个功能的话,要Handler handler=new Handler(Looper.getMainLooper());
2 不用刷新UI ,只是处理消息。
当前消息如果是主线程的话,Handler handler=new Handler ;不是主线程的话,Looper.prepare() Handler handler=new Handler();Looper.loop() 或者Handler handler=new Handle(Looper.getMainLooper());
若是实例化的时候调用Looper.getMainLooper()就表示放到主线程中去处理。
若有不是的话,因为只有UI 线程默认Loop.prepare() Loop.loop()过,其他线程需要手动调用这两个。否则会报错。