写点什么

Android 进阶 (十六) 子线程调用 Toast 报 Can‘t create handler inside thread that has not called Looper.prepare() 错误

  • 2022 年 8 月 16 日
    江苏
  • 本文字数:2113 字

    阅读完需:约 7 分钟

Android进阶(十六)子线程调用Toast报Can‘t create handler inside thread that has not called Looper.prepare() 错误

一、前言

原子线程调用 Toast 报 Can't create handler inside thread that has not called Looper.prepare() 错误

今天用子线程调 Toast 报了一个 Can't create handler inside thread that has not calledLooper.prepare()错误。


因为 toast 的实现需要在 activity 的主线程才能正常工作,所以传统的非主线程不能使 toast 显示在 actvity 上,通过Handler可以使自定义线程运行于 Ui 主线程。


前几次碰到这个问题,确实郁闷了很久... 

java.lang.RuntimeException: Can't create handler inside thread that has not calledLooper.prepare()
复制代码

二、解决方案

解决办法很简单:

Looper.prepare();
Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_LONG).show();
Looper.loop();
复制代码

为什么要加这两句,看了源码就了解了

Toast 

    public void show() {
      ...
        service.enqueueToast(pkg, tn, mDuration);   //把这个toast插入到一个队列里面
        ...
    }
复制代码


Looper

public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
       sThreadLocal.set(new Looper());  //在当前线程中创建一个Looper
    }
private Looper() {
        mQueue = new MessageQueue();  //关键在这,创建Looper都干了什么。 其实是创建了消息队列
        mRun = true;
        mThread = Thread.currentThread();
    }
复制代码


一般如果不是在主线程中又开启了新线程的话,一般都会碰到这个问题。

原因是在创建新线程的时候默认情况下不会去创建新的MessageQueue


总结下:Toast 显示的必要条件:Toast 显示需要出现在一个线程的消息队列中.... 很隐蔽

三、Android 中 HandlerThread 类的解释


Android 应用中的消息循环由 Looper 和 Handler 配合完成,Looper 类用于封装消息循环,类中有个MessageQueue消息队列;Handler 类封装了消息投递和消息处理等功能。


系统默认情况下只有主线程(即 UI 线程)绑定 Looper 对象,因此在主线程中可以直接创建 Handler 的实例,但是在子线程中就不能直接 new 出 Handler 的实例了,因为子线程默认并没有 Looper 对象,此时会抛出 RuntimeException 异常:

 


浏览下 Handler 的默认构造函数就一目了然了:


如果需要在子线程中使用 Handler 类,首先需要创建 Looper 类实例,这时可以通过 Looper.prepare()和 Looper.loop()函数来实现的。阅读 Framework 层源码发现,Android 为我们提供了一个 HandlerThread 类,该类继承 Thread 类,并使用上面两个函数创建 Looper 对象,而且使用 wait/notifyAll 解决了多线程中子线程 1 获取子线程 2 的 Looper 对象为空的问题。


Toast 创建时需要创建一个 Handler,但是这个 Handler 需要获得 Looper 的实例,而在子线程中是没有这个实例的,需要手动创建。


附 Toast 部分源码:


    public Toast(Context context) {
        mContext = context;
        mTN = new TN();
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
    }
    private static class TN extends ITransientNotification.Stub {
        ……
        final Handler mHandler = new Handler();    
        ……
    }
复制代码


Handler 源码:

   /**
     * Default constructor associates this handler with the queue for the
     * current thread.
     *
     * If there isn't one, this handler won't be able to receive messages.
     */
    public Handler() {
        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 = null;
    }
复制代码


发布于: 刚刚阅读数: 3
用户头像

No Silver Bullet 2021.07.09 加入

岂曰无衣 与子同袍

评论

发布
暂无评论
Android进阶(十六)子线程调用Toast报Can‘t create handler inside thread that has not called Looper.prepare() 错误_android_No Silver Bullet_InfoQ写作社区