android 吐司,Android 子线程吐司

测试代码

new Thread(()->{

Toast.makeText(getApplicationContext(),"我弹",Toast.LENGTH_SHORT).show();

}).start();

9cbf40829e1f

Screenshot_1558677502.png

报错信息

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

翻译:无法在没有调用 Looper.prepare() 的线程中创建 handler

报错信息有两点提示:

Toast 需要创建 handler

Handler 里需要有关联的Looper:调用 Looper.prepare

疑问:那么是不是为 Toast 内部的 Handler 关联一个 Looper 就可以成功弹 Toast 了呢?

我们平时在主线程创建 Handler 的时候,内部会获取当前线程关联的 Loper 对象(注意,是获取不是创建)。主线程的 Looper 对象是不需要我们手动创建的,是由应用启动时 Android 系统自动创建的。代码如下:

创建 Handler

public Handler(Callback callback, boolean async) {

...

//获取当前线程关联的Loper对象

mLooper = Looper.myLooper();

//如果当前线程没有关联的Looper,则抛出异常

if (mLooper == null) {

throw new RuntimeException(

"Can't create handler inside thread " + Thread.currentThread()

+ " that has not called Looper.prepare()");

}

...

}

//Looper 获取方法 --> Looper.java

public static @Nullable Looper myLooper() {

//从 ThreadLocal 内获取保存的 Looper

return sThreadLocal.get();

}

//主线程 Looper set 的位置 --> ActivityThread.java

public static void main(String[] args) {

...

Looper.prepareMainLooper();

...

}

//创建主线程 Looper 对象 --> Looper.java

public static void prepareMainLooper() {

prepare(false);

}

//存储 Looper 对象到 ThreadLocal

private static void prepare(boolean quitAllowed) {

sThreadLocal.set(new Looper(quitAllowed));

}

以上是主线程的 Looper 对象的创建流程,由 Android 系统,具体是 ActivityThread.java 中的 mian 方法中调用Looper.prepareMainLooper();

可以看到,只要使用 Handler 对象,就必须为其创建一个关联的 Looper 对象,不然会抛出异常。而我们平时在主线程创建 Handler 的时候之所以不需要为其创建关联的 Looper 对象,是因为系统为我们做了这一步。而 Toast 在子线程使用时报错信息:

Can't create handler inside thread that has not called Looper.prepare()

翻译:无法在没有调用 Looper.prepare() 的线程中创建 handler

这个报错信息显然是因为没有给 Handler 关联其对应的 Looper 造成的。由此我们猜测,Toast 内部是使用到了 Handler 的。去查看下 Toast 源码:

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {

//参2为 Looper 对象,这里默认传入 null

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 Toast(@NonNull Context context, @Nullable Looper looper) {

...

//关键代码#####

mTN = new TN(context.getPackageName(), looper);

...

}

private static class TN extends ITransientNotification.Stub {

...

//内部 Handler

final Handler mHandler;

TN(String packageName, @Nullable Looper looper) {

//这里对指定的looper进行校验,

if (looper == null) {

// 使用 Looper.myLooper() 如果 looper 没有指定

looper = Looper.myLooper();

//#######案发现场#######

if (looper == null) {

throw new RuntimeException(

"Can't toast on a thread that has not called Looper.prepare()");

}

//#######案发现场#######

}

//这里创建了 Handler 并传入指定的 Looper

mHandler = new Handler(looper, null) {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case SHOW: {

...

break;

}

case HIDE: {

...

break;

}

case CANCEL: {

...

break;

}

}

}

};

}

/**

* schedule handleShow into the right thread

*/

@Override

public void show(IBinder windowToken) {

mHandler.obtainMessage(SHOW, windowToken).sendToTarget();

}

/**

* schedule handleHide into the right thread

*/

@Override

public void hide() {

mHandler.obtainMessage(HIDE).sendToTarget();

}

public void cancel() {

mHandler.obtainMessage(CANCEL).sendToTarget();

}

}

通过查看 Toast 的源码我们发现,Toast 的创建过程中,在 TN 这个类中确实创建了一个 Handler,并为其传入了 Looper 对象,这个 Looper 对象在构造 Toast 的过程中传入的一直是 null ,然后调用looper = Looper.myLooper();从 ThreadLocal 中获取当前线程关联的 Looper 对象,而我们在子线程中是没有设置过 Looper 对象的,所以会抛出异常。所以这就是问题所在。要在子线程弹 Toast 就必须为其指定 Looper 。所以我们修改代码:

new Thread(()->{

Looper.prepare();

Toast.makeText(getApplicationContext(),"我弹",Toast.LENGTH_SHORT).show();

Looper.loop();

}).start();

成功~

9cbf40829e1f

Screenshot_1558677336.png

总结:

子线程只是一个普通的线程,其 ThreadLoacl 中没有设置过 Looper,所以会抛出异常,要想子线程弹出 Toast ,需要为其制定 Looper 对象。

Toast 使用的无所谓是不是主线程 Handler,吐司操作的是 Window,不属于 checkThread 抛主线程不能更新 UI 异常的管理范畴。它用 Handler 只是为了用队列和时间控制排队显示吐司。

Toast 内部有两类 IPC 过程,

第一类是 Toast 访问 NotificationManagerService,

第二类是 NotificationManagerService 回调 Toast 里面的 TN 接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值