下面是okhttp的一个简单的异步调用,onResponse 是在子线程中回调的,使用Toast弹出消息时抛出异常
Can't toast on a thread that has not called Looper.prepare()
OkHttpClient okHttpClient=new OkHttpClient.Builder().build();
Request request=new Request.Builder().url("https://www.jianshu.com/p/b1e631a3ea2f").get().build();
Call call=okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//Looper.prepare();
Toast.makeText(MainActivity.this,response.body().string(),Toast.LENGTH_SHORT).show();
//Looper.loop();
}
});
这个异常相信大家不在陌生吧? 那么为什么了?
当我们点开一个app应用时,它的真正程序入口 是在 ActivityThread 类中 的 mian方法
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("" );
Looper.prepareMainLooper();//重点 =================
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
ActivityThread 它所处的是一个主线程,当app启动时,会执行里面的main()方法,执行 main()方法中的Looper.prepareMainLooper(),创建一个消息循环对象,并且这个 Looper对象是全局唯一的
public static void prepareMainLooper() {
prepare(false);//继续执行这个方法 创建全局唯一 主线程Looper对象
synchronized (Looper.class) {
if (sMainLooper != null) {//如果sMainLooper 早已初始化 不为空 就会抛出这个异常
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//首先从sThreadLocal去取 sMainLooper 对象,如果不为空 就抛出异常 这说明它已经被初始化过一次了 想要诱发这个异常 只需要 在Activity中(主线程中),我们就在oncreate()方法中 调用 Looper.prepare(); 这行代码 就会触发这个异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//new 一个Looper对象 把它添加到 sThreadLocal当中去
}
public void set(T value) {
Thread t = Thread.currentThread();//得到当前线程的name值 此时我们app启动时 这时候是主线程 当然其他情况下 这个 Thread 也会是子线程的情况
ThreadLocalMap map = getMap(t);//把 t当作key值取值
if (map != null)//如果这个 ThreadLocalMap 不为空 就是已经初始化过一次了
map.set(this, value);//就直接把这个 looper对象添加进去 当前线程name值作为key值 方便后面取looper
else
createMap(t, value);//否则 新创建一个ThreadLocalMap 并把 looper对象添加进去 当前线程name值作为key值 方便后面取looper
}
至此 我们主线程(全局唯一的一个Looper消息循环对象)就已经创建好了 并且 把它添加进了ThreadLocalMap
这个集合中去
当我们在子线程中 使用 Toast
弹出消息时,
Toast的源码里面 有创建Handler对象,因为我们在子线程中使用Toast,所以new handler()
也是在子线程中
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
我们看看 new Handler()
到底干了什么事情?
public Handler() {
this(null, false);
}
再执行下面的构造方法
public Handler(@Nullable 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();//当 new handler()时,就会执行这句代码 显然是 得到一个Looper对象
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//这个Looper对象 是从sThreadLocal里面去取
}
最终执行到这里来 是在这里取Looper对象的
public T get() {
Thread t = Thread.currentThread();//此时 我们在子线程中使用Toast 这里的t 就是子线程
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();
}
由于我们 只存过 主线程的Looper对象 ,子线程根本没有初始化 就直接取 肯定取不到 ,所以就会抛出此异常,这就是Toast不能直接在子线程中使用,如果想要使用 就添加 Looper.prepare(); 这行代码即可
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//Looper.prepare();
Toast.makeText(MainActivity.this,response.body().string(),Toast.LENGTH_SHORT).show();
//Looper.loop();
}
});