一般我们要显示一个Toast的时候,默认情况下都是在主线程中有如下做法:
Toast.makeText(mContext, "我是一个Toast", Toast.LENGTH_LONG).show();
我们看看makeText里面干了什么:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
默认情况下,会从系统xml文件中获得一个Toast布局文件,也就是我们最终显示出来的样式。在这之前会用构造函数创建一个Toast对象实例。
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
关键看这个内部对象TN,其中有个成员变量是handler,在创建TN对象的时候就初始化了:
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
IBinder token = (IBinder) msg.obj;
handleShow(token);
}
};
我们知道初始化一个Handler对象是必须要有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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
它会去得到当前线程的looper对象,如果得不到就会报异常。
一般情况下我们是在主线程创建的Toast,主线程默认在ActivityThread的main方法中就执行了looper的初始化,所以不会报错。
所以我们如果想在子线程中创建一个Toast对象,那么就必须先创建一个Looper对象。如下:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
Looper.prepare();
Toast.makeText(ThreadTostActivity.this, "我是一个线程里的Toast", Toast.LENGTH_LONG).show();
Looper.loop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
使用Looper.prepare方法在当前线程创建了looper,然后makeText的时候,会创建handler,该handler自然就跟当前线程里的looper对象关联上了。于是就能显示出来了。
相似的,dialog也可以在线程中执行,其内部也是有个成员变量handler,在定义的时候就已经初始化了。
另外使用dialog需要注意的就是必须依附于activity,如果要在service中使用dialog则要另外设置:
http://blog.csdn.net/huxueyan521/article/details/8954844
原因如下:
http://www.jianshu.com/p/413ec659500a
大致就是因为在创建dialog的时候,我们传进来的context的区别,传进来activity的context时parentWindow不会为空,但传进来其他类型的context,则会导致parentWindow为空,一旦为空,接下来在添加到window的时候就报异常了。
另附,activity中的context和application和service的context的区别: