在Android中,规定了只能在主线程(或者叫UI线程)中,去进行UI相关的操作,而其他线程则无法操作UI,否则报错;
但同时,由于不能再UI线程进行耗时的操作,否则会报ANR异常, 因此,我们通常又把耗时操作放到子线程去进行一个处理。
那这就涉及到了一个数据传递的问题,我们在子线程处理的数据,怎么传递到主线程呢?这就有了Handler消息机制。
为什么要设计为只能在主线程进行UI操作而其他子线程则不行呢?
因此,最简单高效的方式就是只让一个线程来处理UI操作,其他线程处理完后,通过handler通知主线程进行更新即可。这是一种典型的生产者-消费者模式, 其他线程生产消息,并把消息放入到一个队列中,而主线程来这个队列取出消息消费。
Android消息机制主要由三部分组成:
需要注意的是:
我们通常在主线程中创建Handler对象后,然后在子线程中处理完耗时操作(IO/网络等),然后再通过handler发送消息。为什么要强调在主线程中创建Handler对象呢? 如果不在主线程创建Handler,就会报一下错误:
java.lang.RuntimeException: Can't create handler inside thread Thread that has not called Looper.prepare()
这是Android开发一个很常见的错误,如果想要在非主线程使用handler,就必须在线程的run方法的第一行调用Looper.preper()方法, 在run方法的最后一行调用Looper.loop()方法, 才行, 不过这个子线程的Handler不能处理UI相关的消息。
所以我们通常在Activity中或者Service的代码块中创建Handler对象, 或者在创建Handler时,传入主线程的Looper对象,如下:
Handler handler = new Handler(Looper.getMainLooper());
因为只有这样,我们创建的Handler对象才能把消息发送到主线程的MessageQueue中,才能被主线程中的Looper取出进行处理。
具体源码,在下面第5小节源码分析。
因为handler发送的消息是在被主线程中被Looper从MessageQueue中取出来进行处理的,所以这一段代码自然是运行在主线程中的。
具体源码分析见第5小节。
我们老是说主线程,主线程,到底什么是主线程呢?其实Android中的主线程,就是ActivityThread,而我们一个APP进程的入口函数,就是ActivityThread.main(), 主要代码如下:
public static void main(String[] args) {
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到,在main方法的开始位置,就调用了 Looper.prepareMainLooper(), 完成了对主线程Looper对象的初始化,然后在结束位置调用了 Looper.loop(), 这个方法就是一个死循环,不断去MessageQueue中读取消息。因此,执行了Looper.loop()后,在此方法之后的代码理论上就不会被执行,因此在最后抛出一个RuntimeException, 因为如果执行到这一句,那肯定是程序出错了。
我们看一下这个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.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); //注释1、去MessageQueue中获取消息,如果没有消息,则阻塞在这里
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);//注释2、此处回调给Handler的handleMessage方法或传入的Runnable接口进行处理
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
上述代码中的注释1,是从MessageQueue中去取出一个消息,如果暂时没有消息,则阻塞在这里,直到来了新消息。
注释2 回调Handler对象的handleMessage方法进行代码处理,如果创建Handler对象时没有重写handleMessage,而是传入的Runnable或Callback接口,则调用这两个接口的方法。
所有的这一段代码都是执行在ActivityThread.main()方法中的,所以是运行在主线程中的。
前面我们将Handler消息机制的内容从整体上就讲完了,但是还有一些细节没有说,这里补充一个很重要的知识点:ThreadLocal类。这个类日常开发中用的不多,但是在某些场景下却可以帮助我们轻松实现一些看起来复杂的问题。在Android源码中其中一个使用场景就是Handler。
一般来说,当某个数据的作用域是在某个线程时,而不同线程可能有不同数据时,可以考虑使用ThreadLocal。类似于我们new了一个对象,这个类中某个字段在不同对象中,它的值可能不同,但是这个和ThreadLocal不同的是,类中的字段时我们写类的代码时就定义好的,但是Thread线程对象中一开始并没有我们想要的某个字段,因此,我们可以通过ThreadLocal来额外添加。
比如以下代码:
private static ThreadLocal mThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
mThreadLocal.set(true);//main线程设为true
new Thread() {
public void run() {
mThreadLocal.set(false);
System.out.println("Thread 111 mThreadLocal.get: "+ mThreadLocal.get());
};
}.start();
new Thread() {
public void run() {
System.out.println("Thread 222 mThreadLocal.get: "+ mThreadLocal.get());
};
}.start();
System.out.println("Thread main mThreadLocal.get: "+ mThreadLocal.get());
}
执行的结果为:
Thread 111 mThreadLocal.get: false
Thread main mThreadLocal.get: true
Thread 222 mThreadLocal.get: null
三个线程执行的先后顺序不同,但看结果还是比较明显的, 在main线程,我们设置为true,获取的值也是true,在111线程设置为false,获得的结果就是false, 而线程222没有设置任何值, 结果为null。
通过ThreadLocal,我们可以为APP进程设置一些作用域为线程的变量,简单方便,而如果不用ThreadLocal,通常的做法就是, 要么设置回调接口, 一层一层往下传递,要么设置静态接口, 或者设置静态变量来调用。 如果是接口,传递代码过深,跟踪起来会比较麻烦,而且每个方法形参都要传入一个接口,设计得比较复杂,而如果使用静态变量,那需要很多值呢,岂不是的设置很多不同的变量名称,代码看起来会比较冗余。
而使用ThreadLocal,直接在当前线程中调用set方法,然后在当前线程需要的地方调用get方法即可,简单高效。这个代码设计简直太优秀了!
在上述代码中,我们看到,要使用ThreadLocal,通常要指定一个泛型,然后调用set的时候,传入这个泛型即可,调用get即可得到我们设置的值。
以上就是关于Handler的知识点,如果您觉得内容还行,麻烦点个赞吧~~~