Android开发中,我们经常会遇到这样的一个场景:子线程获取数据,主线程将子线程获取到的数据进行UI更新,而Android本身提供的是Handler来处理,那handler是如何实现的呢?它的原理又是什么呢?
主要分下面几个部分来进行分析:
1.如果使用?
2.子线程发送的消息为什么在主线程收到?
3.简单介绍一下ThreadLocal。
开始进入正题:
1、如何使用
1.1新建一个Handler对象
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
1.2将获取到的数据(暂时定义为null)发送个handler进行UI更新。
new Thread(new Runnable() {
@Override
public void run() {
handler.sendMessage(null);
}
}).start();
第一部分就完了,也是最简单的如何使用。
2、子线程发送的消息为什么在主线程收到?
2.1 子线程发送消息
跟踪handler.sendMessage()方法,调用过程:
sendMessage() --> sendMessageDelayed() --> sendMessageAtTime() --> enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
...
return queue.enqueueMessage(msg, uptimeMillis);
...
}
由上述代码可以看出,发送过程已经结束,即将此过程称之为消息入队
。
2.2主线程接受消息
我们一定听过这样一句话,主线程有一个默认的Looper实例,用它来轮训消息队列。那它在哪实例化的呢?
在ActivityThread.java中,有一个main方法,它是应用程序的入口。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
...
}
在这个方法中,有两条重要的语句
先看第一条,调用过程如下:
Looper.prepareMainLooper() --> prepare()
这个过程很简单,看一下prepare中都做了什么。
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
没错,在这个方法中实例化了Looper,并将其存储到ThreadLocal。
再看第二条,直接看looper的方法:
public static void loop() {
final Looper me = myLooper();
msg.target.dispatchMessage(msg);
}
loop()方法有两个作用,第一是从ThreadLocal中获取looper的实例;第二是从消息队列中取到消息之后进行消息分发,看一下dispatchMessage方法:
public void dispatchMessage(Message msg) {
...
handleMessage(msg);
}
OK,终于看到了Handler的消息回调方法,因为loop方法是在主线程中执行的,所以handleMessage也是在主线程进行回调的。
3、简单介绍一下ThreadLocal。
主要是为了保证java多线程并发中数据的隔离,简而言之,多个线程之间不会有共享的数据,所有数据都是线程私有的。
那ThreadLocal在Handler中是怎么体现的呢?
其中在实例化Looper对象的时候,调用了set方法,而在获取Looper对象的时候,调用了get方法,那我们分别看一下set和get都做了什么?
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在ThreadLocal中set数据的时候,其实是在每个线程的ThreadLocalMap中写值。每个线程都有自己的ThreadLocalMap, 它的key为threadlocal对象,value为Looper实例。注意:网上好多人说key是thread对象,其实看源码不是的。
get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
了解set的原理之后,get也就很简单了,就是从一个map中取值。
最后用一张很简单的图来总结一下:
本人愚见,望起到抛砖引玉作用,欢迎各位指正。