Toast
也是基于Window实现,可以定时取消,内部采用了Handler
。
两种IPC进程方式,分别是NotificationManagerService
与NotificationManagerService
回调内部的TN接口实现。
它可以自定义view,也可以使用默认的view,默认的就是mNextView。当前view的显示与取消都是一次跨进程通信。也就是show与hide方法。
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
}
}
public void cancel() {
mTN.cancel();
}
TN
一个跨进程通信,在Toast
与NotificationManagerService
进行进程通信时候,回调TN方法运行在Binder线程池中,所有需要handler进行切换到当前线程,而handler切换线程操作必须通过looper才能够完成,所有首先进行非空判断操作,再进行切换线程。
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
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: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
}
};
Toast
的show方法中调用了service.enqueueToast(pkg, tn, mDuration);
,这里的Service
实质上是NotificationManagerService
,也就是调用了NotificationManagerService
中的enqueueToast
方法,这个方法运行在服务端中,然后再Toast类中通过getService
来获取NotificationManagerService
实例对象。
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
第一个参数表示当前包名,第二个参数表示远程回调,第三个参数表示Toast时长。
方法内部将Toast对象封装成ToastRecord
对象,然后添加到mToastQueue
队列中,队列中最多存储50个ToastRecord对象,主要为了防止DDOS。
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
添加到队列中,通过showNextToastLocked来。
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
//通过callback来进行展示,这里是一个远程进程通信,通过callback来访问TN中的方法
record.callback.show(record.token);
//内部还会发送一个延迟消息
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);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
延迟消息scheduleTimeoutLocked,有两种一种是LONG_DELAY
:3.5秒,SHORT_DELAY
:2秒。
private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
Toast隐藏也是通过callback实现跨进程通信,访问TN中的hide方法。
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
}
ToastRecord lastToast = mToastQueue.remove(index);
mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
showNextToastLocked();
}
}
TN内部两个方法show与hide方法
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
都是通过handler发送一个消息,再切换到当前线程中分别调用handleShow
与handleHide
。
handleShow
将Toast添加到Window中
mWM.addView(mView, mParams);
handleHide
将Toast从Window中移除
mWM.removeViewImmediate(mView);