Android Toast

问:Toast 只能在主线程使用吗?

答:并不一定在主线程,只要 Toast show 的线程有可用 Looper 对象即可(即 Hanlder 可用),主线程默认有 Looper,其他线程默认没有,具体用法如下:

//主线程,直接可用
Toast.makeText(context, "xxx", Toast.LENGTH_SHORT).show();
//子线程提供Looper也可用
new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        Toast.makeText(context, "xxx", Toast.LENGTH_SHORT).show();
        Looper.loop();
    }
}.start();
//崩溃,提示Can't toast on a thread that has not called Looper.prepare()
new Thread() {
    @Override
    public void run() {
        Toast.makeText(context, "xxx", Toast.LENGTH_SHORT).show();
    }
}.start();

至于原因我们可以看下 Toast 源码,如下:

//Toast的实例创建一定要在具备Looper的线程中创建,原理分析
public class Toast {
    ......
    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        //创建TN对象,makeText方法默认looper是空
        mTN = new TN(context.getPackageName(), looper);
        ......
    }
    //创建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;
    }
    public void show() {
        ......
        //对应 NotificationManagerService
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
    private static class TN extends ITransientNotification.Stub {
        ......
        //创建Toast实例对象时会以组合模式创建TN实例,默认传入looper为null。
        TN(String packageName, @Nullable Looper looper) {
            ......
            //如果Looper为空,则使用当前线程的Looper,主线程默认有Looper,子线程默认没有
            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    //如果子线程没有Looper.prepare()准备Looper对象则抛出异常
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            ......
                            break;
                        }
                        case HIDE: {
                            ......
                            break;
                        }
                        case CANCEL: {
                            ......
                            break;
                        }
                    }
                }
            };
        }
        ......
    }
}

本文参考自 面试题:Toast 只能在主线程使用吗?

你可能感兴趣的:(Android Toast)