常规使用
Toast.makeText(mActivity, "hi", Toast.LENGTH_SHORT).show();
Toast.make(
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
// 创建Toast类
Toast result = new Toast(context, looper);
// 布局加载器 加载 transient_notification
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
// Toast类中包含有 Toast的View ,mDuration 字段
result.mNextView = v;
result.mDuration = duration;
return result;
}
new Toast(context, looper)
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
// 创建 ToastNotification 传入 looper
mTN = new TN(context.getPackageName(), looper);
// 获取尺寸
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
//设置布局相对位置
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
new Tn(context.getPackageName(),looper)
// TN 继承代理接口 主要 show hide cancel
private static class TN extends ITransientNotification.Stub {
...
private static final int SHOW = 0;
private static final int HIDE = 1;
private static final int CANCEL = 2;
final Handler mHandler;
...
View mView;
View mNextView;
int mDuration;
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
TN(String packageName, @Nullable Looper 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()");
}
}
// 根据传入looper 创建处理handler
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;
}
}
}
};
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show(IBinder windowToken) {
// 发送 SHOW 消息
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();
}
show方法
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
// 获取 NotificationManagerService 通知管理服务
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
// 执行 NotificationManagerService.enqueueToast 进行消息转发
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
NotificationManagerService.enqueueToast
// 这里的callback ITransientNotification ,TN继承的就是 ITransientNotification.stub
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
..
final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
final boolean isPackageSuspended =
isPackageSuspendedForUser(pkg, Binder.getCallingUid());
// areNotificationsEnabledForPackage 判断是否有开启通知
if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
(!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
|| isPackageSuspended)) {
return;
}
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
//取出与当前包名匹配的 ToastRecord
int index = indexOfToastLocked(pkg, callback);
//从队列中获取 Toast信息
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else { // 找不到与当前包名匹配的ToastRecord
// 创建一个新的加入队列
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
// 匹配数量>50 后续不再处理
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
return;
}
}
}
}
// 将新ToastRecord添加到队列
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
//index=0 表示是当前需要执行的Toast
if (index == 0) {
// 处理下一条 Toast
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
showNextToastLocked
@GuardedBy("mToastQueue")
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
// 调用TN的 show 进行展示Toast
record.callback.show(record.token);
// 发送延时消息 进行关闭Toast
scheduleTimeoutLocked(record);
return;
}
}
scheduleTimeoutLocked
@GuardedBy("mToastQueue")
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); //发送延时消息 根据长短标签
}
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
//消息延时后处理
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
...
// timeout 后处理
private void handleTimeout(ToastRecord record)
{
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index); //取消toast
...
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
// 调用TN的隐藏
record.callback.hide();
TN show hide
private static class TN extends ITransientNotification.Stub {
...
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();
...
@Override
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
@Override
public void hide() {
mHandler.obtainMessage(HIDE).sendToTarget();
}
// 添加view到wm
public void handleShow(IBinder windowToken) {
...
if (mView != mNextView) {
handleHide();
...
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
// 从wm移除view
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
mWM.removeViewImmediate(mView);
}
mView = null;
}
}
总结
Toast
提供统一对外api方法,实际执行由Tn,或其他组件执行
show操作执行时,调用 NotificationManagerService.enqueueToast
Tn 消息具体实现代理类
内部维持handler 绑定外部传入looper (系统隐藏了 构造,默认主线程looper)
show hide cancel 具体实现 再NotificationManagerService调用Tn show hide时通过handler引导进正确的线程进行处理Toast
NotificationManagerService
内部维持 ArrayList<ToastRecord> mToastQueue 队列,Toast.show 时封装ToastRecord加入队列,并从Queue中获取ToastRecord,调用Tn.show执行,
内部维持handler 进行消息延时处理
通知发送延时消息,延时调用Tn.hide 移除Toast