Toast show的时候崩溃的问题

来源:互联网 发布:今日头条mac客户端 编辑:程序博客网 时间:2024/04/30 06:20

今天同事提交了一份代码,里面有一个简单的Toast.makeText(`````).show(),没想到真正运行的时候跑到这里挂掉了:

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


从崩溃上看这是looper没有启动导致的,但是为什么? 查询源码后找到了原因(以6.0源码为例):

先看Toast的show方法:

    /**     * Show the view for the specified duration.     */    public void show() {        if (mNextView == null) {            throw new RuntimeException("setView must have been called");        }        INotificationManager service = getService();        String pkg = mContext.getOpPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try {            service.enqueueToast(pkg, tn, mDuration);        } catch (RemoteException e) {            // Empty        }    }

这里直接交给了INotificationManager来进行显示的操作,按照android的命名规则,这个AIDL的实现位于NotificationManagerService中:

<span style="white-space:pre"></span>@Override        public void enqueueToast(String pkg, ITransientNotification callback, int duration)        {            ......            synchronized (mToastQueue) {                int callingPid = Binder.getCallingPid();                long callingId = Binder.clearCallingIdentity();                try {                    ToastRecord record;                    int index = indexOfToastLocked(pkg, callback);                    // If it's already in the queue, we update it in place, we don't                    // move it to the end of the queue.                    if (index >= 0) {                        record = mToastQueue.get(index);                        record.update(duration);                    } else {                        // Limit the number of toasts that any given package except the android                        // package can enqueue.  Prevents DOS attacks and deals with leaks.                        
<span style="white-space:pre"></span>......                        record = new ToastRecord(callingPid, pkg, callback, duration);                        mToastQueue.add(record);                        index = mToastQueue.size() - 1;                        keepProcessAliveLocked(callingPid);                    }                    // If it's at index 0, it's the current toast.  It doesn't matter if it's                    // new or just been updated.  Call back and tell it to show itself.                    // If the callback fails, this will remove it from the list, so don't                    // assume that it's valid after this.                    if (index == 0) {                        showNextToastLocked();                    }                } finally {                    Binder.restoreCallingIdentity(callingId);                }            }        }

一系列的判断后,交给了showNextToastLocked方法:

     void showNextToastLocked() {        ToastRecord record = mToastQueue.get(0);        while (record != null) {            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);            try {                record.callback.show();                scheduleTimeoutLocked(record);                return;            } catch (RemoteException e) {                Slog.w(TAG, "Object died trying to show notification " + record.callback                        + " in package " + record.pkg);                // remove it from the list and let the process die                int index = mToastQueue.indexOf(record);                if (index >= 0) {                    mToastQueue.remove(index);                }                keepProcessAliveLocked(record.pid);                if (mToastQueue.size() > 0) {                    record = mToastQueue.get(0);                } else {                    record = null;                }            }        }    }

最终的展示是在这句:record.callback.show(),这个record.callback其实是由Toast通过AIDL传递过来的,回头再看Toast的代码,其实是调用了一个内部类TN.show():

        /**         * schedule handleShow into the right thread         */        @Override        public void show() {            if (localLOGV) Log.v(TAG, "SHOW: " + this);            mHandler.post(mShow);        }
而这个mHandler也就是我们报错时所提示的looper所在,它的创建就是一句简单的new而已:

    private static class TN extends ITransientNotification.Stub {        
<span style="white-space:pre"></span>......        
<span style="white-space:pre"></span>private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();        final Handler mHandler = new Handler();
<span style="white-space:pre"></span>
    }

至此也就搞清楚了这个崩溃的根本原因了,运行Toast.show()的线程,自身的looper并没有开始工作,所以导致了这个崩溃。

0 0
原创粉丝点击