问题重现
在一个回调里,我写了如下代码,
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
downloadAvatar();
}
}, 3000);//3秒后执行
结果抛了运行时异常
E/AndroidRuntime: FATAL EXCEPTION: Timer-2
Process: com.purplestoneai.machine, PID: 17296
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at com.purplestoneai.machine.activity.MealActivity$9.uartMsg(MealActivity.java:424)
问题原因
查资料发现了这些信息:Handler对象与它的调用者在同一个线程里。如果在Handler中设置了延时操作,那么它的调用线程也会堵塞。每一个Handler对象都会绑定一个Looper对象,每一个Looper对象对应一个消息队列。如果在创建Handler时不指定与其绑定的Looper对象,系统默认会将当前线程的Looper绑定到该Handler上。
在主线程中,可以直接使用new Handler()创建Handler对象,其将自动与主线程的Looper对象绑定;在非主线程中直接这样创建Handler则会报错,因为Android系统默认情况下非主线程没有开启Looper,而Handler对象必须绑定Looper对象。因此会抛运行时异常。
解决方案
方案1
//开启Looper
Looper.prepare();
//这种情况下,Runnable对象是运行在子线程中的,可以进行联网操作,但是不能更新UI
new Handler().post(new Runnable() {
@Override
public void run() {
try {
//休眠3秒
Thread.sleep(3000);
//doSomething
downloadAvatar();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Looper.loop();
方案2
//创建一个绑定在主线程Looper上的句柄
//Looper.getMainLooper()获得主线程的Looper
Handler handler = new Handler(Looper.getMainLooper());
//这种情况下,Runnable对象是运行在主线程中的,不可以进行联网操作,但是可以更新UI
handler.post(new Runnable() {
@Override
public void run() {
try {
//休眠3秒
Thread.sleep(3000);
//doSomething
downloadAvatar();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
以上代码均写在回调函数或者某一个子线程中。
需要注意的是,即使代码中没有Handler,也是可能抛这个异常的。比较经常出现的情况是在子线程中直接Toast了。Toast只能在UI线程弹出,如果一定要在子线程中弹,那么需要通过方案2,new一个绑定在主线程Looper上的Handler,在Runnable对象的run方法中Toast。