但是子线程中真的不能直接显示Toast吗?

答案是:当然可以。

那应该怎么操作呢?在当前线程中先初始化一个Looper即可!

Looper.prepare();
 Toast.makeText(getBaseContext(), “text”, Toast.LENGTH_LONG).show();
 Looper.loop();

为什么在子线程中使用Toast需要初始一个Looper呢? 我们看看源代码:

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
 return makeText(context, null, text, duration);
 }public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
 @NonNull CharSequence text, @Duration int duration) {
 Toast result = new Toast(context, looper);
 …
 return result;
 }

以上是我们使用Toast时调用的静态方法,可以看到第二个方法有个参数Looper,虽然我们平时用的时候都传入的是null,那这个Looper究竟有什么用呢?我们看看Toast的构造函数:

public Toast(@NonNull Context context, @Nullable Looper looper) {
 mContext = context;
 mTN = new TN(context.getPackageName(), looper);
 }

可以看出这个Looper其实是TN在用,我们看看它的构造函数:

TN(String packageName, @Nullable Looper looper) {
 if (looper == null) {
 // Use Looper.myLooper() if looper is not specified.
 looper = Looper.myLooper();
 if (looper == null) {
 throw new RuntimeException(
 “Can’t toast on a thread that has not called Looper.prepare()”);
 }
 }}

以上代码有简化。可以看出当Looper为null的时候,会通过Looper.myLooper获取一个当前的Looper。我们知道在主线程中系统已经为我们初始化了一个mainLooper,所以我们一般不用管。但是当我们子线程中如果没有初始化Looper,这里调用Looper.myLooper就获取不到一个Looper,则会抛出异常。所以当我们在子线程中使用Toast,使用Looper.prepare()方法初始化一个Looper并用Looper.loop()让它启动起来即可。

所以我们可以封装一个可以在任何线程使用的Toast。

private static Toast toast = null;
 public static void showToast(Context context, String text) {
 Looper myLooper = Looper.myLooper();
 if (myLooper == null) {
 Looper.prepare();
 myLooper = Looper.myLooper();
 }