1、在子线程中toast会报如下异常
Can't toast on a thread that has not called Looper.prepare()
2、要想在子线程中弹Toast需要 如下:结果就会在子线程中弹出Toast
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "my toast", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
3、解释以上现象
首先,Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里面的TN接口。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
// 此处使用到了系统服务,也就是一类IPC
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
// TN是本地的Binder类,当NMS处理Toast的显示请求是会回调TN中的方法
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
private static class TN extends ITransientNotification.Stub {
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();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
}
};
}
/**
* 该方法会被NMS处理显示请求时候回调,由于该方法运行在binder线程池中
* 所以需要使用Handler将其切换到当前线程中
*/
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
可以看到NMS回调TN中的show方法,show方法又运行在binder线程池中,所以使用了handler将其切换到当前线程中,而当前线程就是发送Toast请求所在的线程,而handler需要looper才能完成切换线程的功能。