您的位置:首页 > 移动开发 > Android开发

Toast show的时候崩溃的问题

2016-10-21 17:58 267 查看
今天同事提交了一份代码,里面有一个简单的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并没有开始工作,所以导致了这个崩溃。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android
相关文章推荐