java handler线程_Handler源码解析——子线程创建Handler为何报错?

本文探讨了Android中的Handler和Looper在线程通信中的作用,以及为何在子线程中创建Handler会抛出异常"Can't create handler inside thread that has not called Looper.prepare()"。通过分析Handler和Looper的源码,解释了Handler依赖于Looper,而Looper需要在调用Looper.prepare()后才能被创建。文章还介绍了ThreadLocal在其中的角色,帮助读者理解线程间数据存储的概念。
摘要由CSDN通过智能技术生成

前言

Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信。这是两个完全不同的概念。

Handler先进先出原则,Looper类用来管理特定线程内消息的交换(MessageExchange);

1、为什么会有Handler机制?

我们刚说Handler机制的主要作用是将某一任务切换到特定的线程来执行,我们做项目可能都遇到过ANR(Application Not Response),这就是因为执行某项任务的时间太长而导致程序无法响应。这种情况我们就需要将这项耗时较长的任务移到子线程来执行,从而消除ANR。而我们都知道Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。而Android提供Handler就是为了解决在子线程中无法访问UI的矛盾。

2、Handler源码解析

子线程中创建Handler为啥会报错?

首先,我们先看一个例子,我们在子线程中创建一个Handler。

new Thread(new Runnable() {

@Override

public void run() {

new Handler(){

@Override

public void handleMessage (Message message){

super.handleMessage(message);

}

};

}

},"MyThread").start();

我们运行时会发现,会抛出异常:Can't create handler inside thread that has not called Looper.prepare()

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

at android.os.Handler.(Handler.java:204)

at android.os.Handler.(Handler.java:118)

at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1$1.(MainActivity.java:21)

at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1.run(MainActivity.java:21)

at java.lang.Thread.run(Thread.java:764)

究竟是为什么会抛出异常呢?下面我们通过Handler源码来看看。

Handler的构造方法

当我们创建Handler对象的时候调用的是下面的方法:

/**

* Default constructor associates this handler with the {@link Looper} for the

* current thread.

*

* If this thread does not have a looper, this handler won't be able to receive messages

* so an exception is thrown.

*/

public Handler() {

this(null, false);

}

我们看到注释中说:++默认的构造函数将这个Handler与当前的线程的Looper关联,如果当前线程没有Looper,那么这个程序将无法接收消息,因此会抛出异常。++ 究竟是怎么抛出异常的呢?我们继续往下看:

public Handler(Callback callback, boolean async) {

if (FIND_POTENTIAL_LEAKS) {

final Class extends Handler> klass = getClass();

if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&

(klass.getModifiers() & Modifier.STATIC) == 0) {

Log.w(TAG, "The following Handler class should be static or leaks might occur: " +

klass.getCanonicalName());

}

}

mLooper = Looper.myLooper();//注释1

if (mLooper == null) {

throw new RuntimeException(//注释2

"Can't create handler inside thread that has not called Looper.prepare()");

}

mQueue = mLooper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

我们看到在这里开始有个if语句,由于FIND_POTENTIAL_LEAKS默认值是false所以我们不需要去管它,注释1处,这里调用了Looper.myLooper(),我们看看它的源码:

Looper.myLooper()

/**

* Return the Looper object associated with the current thread. Returns

* null if the calling thread is not associated with a Looper.

*/

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

这个方法的作用就是返回与当前线程相关连的Looper对象。这里又调用了ThreadLocal.get(),

ThreadLocal

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只有在特定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到。ThreadLocal用一句大白话来讲解,++就是看上去只new了一份,但在每个不同的线程中却可以拥有不同数据副本的神奇类。++ 其本质是ThreadLocal中的Values类维护了一个Object[],而每个Thread类中有一个ThreadLocal.Values成员,当调用ThreadLocal的set方法时,其实是根据一定规则把这个线程中对应的ThreadLocal值塞进了Values的Object[]数组中的某个index里。这个index总是为ThreadLocal的reference字段所标识的对象的下一个位置。

下面我们来看它的get方法。

ThreadLocal.get()

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

在该方法的第二行调用了getMap方法。就是去获取当前线程的ThreadLocalMap对象。但是这个对象是在什么时候创建的呢?

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

因为抛出异常了所以我们猜测这个值可能是null。说到这里可能有些迷茫,我们回头看看注释1处那里紧接着,我们看到如果获取到的myLooper为空(至于为什么会返回为空我们看完后面回过头来看就很明白了)的的话,就会抛出我们前面看到的异常。

......

mLooper = Looper.myLooper();//注释1

if (mLooper == null) {

throw new RuntimeException(//注释2

"Can't create handler inside thread that has not called Looper.prepare()");

}

......

异常中说如果在子线程中创建Handler必须要调用Looper.prepare()方法,那么我们想肯定是在调用该方法的时候做了一些操作,可能跟后面消息的接收和处理相关,通过后面的源码我们会发现其实这个方法是对当前线程创建一个Looper对象,我们来看源码:

Looper.prepare()

public static void prepare() {

prepare(true);

}

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {//注释3

throw new RuntimeException("Only one Looper may be created per thread");

}

sThreadLocal.set(new Looper(quitAllowed));

}

我们调用是没有传参的prepare,他会调用内部的传参的prepare方法。我们看到注释3处又调用了ThreadLocal.get(),假设我们第一次调用Looper.prepare(),那么这个值肯定是空的。如果不是空的话前面Looper.myLooper()就不会为空,也就不会抛出异常了。(那么当在一个线程中第二次调用该方法的时候,他的返回值就不会是空,系统会抛出异常一个线程只能创建一个Looper。也就是说一个子线程中Looper.prepare()只能调用一次。)所以这里肯定走ThreadLocal.set()方法,并且新建了一个Looper对象作为入参:

ThreadLocal.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);

}

我们看到第二行还是调用了getMap由于第一次调用,所以这个返回值还是空的。所以应该走了createMap(),下面我们看看它的源码:

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

我们看到这里才对当前线的ThreadLocal进行了赋值。这个方法中我们看到ThreadLocal已Map的结构存储了当前线程对应的Looper。以线程为Entry也就是Key,以Looper为Value。我们回过来再看,Looper.myLooper()。

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);//注释4

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

其实发现他就是去获取当前线程的Looper。当没有调用Looper.prepare()来创建Looper时,当前线程的ThreadLocalMap对象为空,所以前面的ThreadLocal.get()方法会调用setInitialValue这个方法,

private T setInitialValue() {

T value = initialValue();

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

return value;

}

我们看到这个方法返回了value,value是通过一个方法返回的,下面我们看看这个方法:

protected T initialValue() {

return null;

}

这个方法单纯的就是返回null,所以也就是相当于ThreadLocal.get()返回null===>Looper.myLooper()返回null。所以Handler会在注释2处抛出异常。

总结

回过头来,我们仔细想想为什么会抛出异常来?就是因为:

Handler对象是基于Looper的,每个Handler必须有一个Looper,这个Looper是在调用Looper.prepare()的时候创建的,这个Looper会以Map的形式存储在当前线程的ThreadLocal中。当当掉用Looper.myLooper()方法就是去在当前线程的ThreadLocal中拿到当前线程的Looper对象。

欢迎在评论区留下你的观点大家一起交流,一起成长。如果今天的这篇文章对你在工作和生活有所帮助,欢迎转发分享给更多人。

同时欢迎大家加入我组建的大前端学习交流群,从这里出发我们一起讨论,一起交流,一起提升。

群号:872749114

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值