使用方式

一般使用

Toast.makeText(this,"Short Toast", Toast.LENGTH_SHORT).show();

自定义Toast,通过如下接口注入自定义视图

##Toast.java##
public void setView(View view) {mNextView = view;
}

接下来步入正题,Toast的显示流程

Toast流程分析

显示show()

  1. 从show方法开始,自定义toast会创建Toast.TN binder对象,用于与NMS交互;最终将toast相关信息转入NMS入队
##Toast.java##public void show() {//..//1.获取NMSINotificationManager service = getService();String pkg = mContext.getOpPackageName();//2.创建TN,用于与NMS通信的binder对象,自定义的toast会用到TN tn = mTN;//3.自定义toast视图tn.mNextView = mNextView;final int displayId = mContext.getDisplayId();//4.调用NMS接口toast入队(主要区别为自定义toast会传入TN参数,后续用来与NMS交互)try {if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {if (mNextView != null) {// It's a custom toastservice.enqueueToast(pkg, mToken, tn, mDuration, displayId);} else {// It's a text toastITransientNotificationCallback callback =new CallbackBinder(mCallbacks, mHandler);service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);}} else {service.enqueueToast(pkg, mToken, tn, mDuration, displayId);}} catch (RemoteException e) {// Empty}
}
  1. NMS中的入队流程,入队接口最终汇入同一主流;显示会进行一系列前置条件判断;之后会判断当前toast是否存在于队列中,存在则直接刷新;反之,则将消息封装成ToastRecord则进行入队;
    若入队为首个,则直接开始调度显示showNextToastLocked()
    PS:入队之前还会有一个门槛,NMS会有单应用toast个数阈值,阈值以下,正常入队,阈值以上则直接拦截;直到该应用toast个数消费小于阈值后可正常入队(推测这也是android的一种防攻击降负载的策略)
##NotificationManagerService.java##final IBinder mService = new INotificationManager.Stub() {@Overridepublic void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,int displayId, @Nullable ITransientNotificationCallback callback) {enqueueToast(pkg, token, text, null, duration, displayId, callback);}@Overridepublic void enqueueToast(String pkg, IBinder token, ITransientNotification callback,int duration, int displayId) {enqueueToast(pkg, token, null, callback, duration, displayId, null);}private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,@Nullable ITransientNotification callback, int duration, int displayId,@Nullable ITransientNotificationCallback textCallback) {//...synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index = indexOfToastLocked(pkg, token);// 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) {//1.相同toast只更新时长,不改变位置record = mToastQueue.get(index);record.update(duration);} else {// Limit the number of toasts that any given package can enqueue.// Prevents DOS attacks and deals with leaks.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++;//2.队列中同包名toast大于阈值,则直接return,不入队if (count >= MAX_PACKAGE_NOTIFICATIONS) {Slog.e(TAG, "Package has already posted " + count+ " toasts. Not showing more. Package=" + pkg);return;}}}Binder windowToken = new Binder();mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId);//3.将toast信息封装为ToastRecordrecord = getToastRecord(callingUid, callingPid, pkg, token, text, callback,duration, windowToken, displayId, textCallback);//4.入队mToastQueue.add(record);index = mToastQueue.size() - 1;keepProcessAliveForToastIfNeededLocked(callingPid);}// If it's at index 0, it's the current toast.  It doesn't matter if it's// new or just been updated, show it.// If the callback fails, this will remove it from the list, so don't// assume that it's valid after this.if (index == 0) {//5.显示toastshowNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}
  1. 再来看下Toast信息封装成ToastRecord部分getToastRecord();ToastRecord为抽象类,实现类分别为:
    TextToastRecord :默认文本类Toast
    CustomToastRecord :自定义Toast
    此处的callback实现即为Toast.TN,自定义Toast时会创建传入非空
##NotificationManagerService.java##private ToastRecord getToastRecord(int uid, int pid, String packageName, IBinder token,@Nullable CharSequence text, @Nullable ITransientNotification callback, int duration,Binder windowToken, int displayId,@Nullable ITransientNotificationCallback textCallback) {if (callback == null) {return new TextToastRecord(this, mStatusBar, uid, pid, packageName, token, text,duration, windowToken, displayId, textCallback);} else {return new CustomToastRecord(this, uid, pid, packageName, token, callback, duration,windowToken, displayId);}
}
  1. 接着第2点,NMS中调度Toast显示showNextToastLocked(),while循环,取出列表首个record进行显示
##NotificationManagerService.java##void showNextToastLocked() {ToastRecord record = mToastQueue.get(0);while (record != null) {//Toast显示if (record.show()) {//成功则启动退出的延迟计时scheduleDurationReachedLocked(record);return;}//失败则取出下一个int index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;}
}
  1. ToastRecord中的show(),如第3点可知,此处存在两种业务
    (1) 先看默认文本类Toast:TextToastRecord ;此处的mStatusBar为StatusBarManagerService的binder代理
##TextToastRecord.java##
//...
private final StatusBarManagerInternal mStatusBar;
//...
@Override
public boolean show() {if (DBG) {Slog.d(TAG, "Show pkg=" + pkg + " text=" + text);}if (mStatusBar == null) {Slog.w(TAG, "StatusBar not available to show text toast for package " + pkg);return false;}mStatusBar.showToast(uid, pkg, token, text, windowToken, getDuration(), mCallback);return true;
}

再来看StatusBarManagerService,又会通过IStatusBar的binder将信息传入下一级,IStatusBar又是哪位的代理?!SystemUI

##StatusBarManagerService.java##private volatile IStatusBar mBar;
@Override
public void showToast(int uid, String packageName, IBinder token, CharSequence text,IBinder windowToken, int duration,@Nullable ITransientNotificationCallback callback) {if (mBar != null) {try {mBar.showToast(uid, packageName, token, text, windowToken, duration, callback);} catch (RemoteException ex) { }}
}

SystemUI中存在一个叫CommandQueue的类,实现了该binder接口;之后Handler消息调度,将消息传递至CommandQueue.Callbacks转出

##CommandQueue##public class CommandQueue extends IStatusBar.Stub implements CallbackController<Callbacks>,DisplayManager.DisplayListener {@Overridepublic void showToast(int uid, String packageName, IBinder token, CharSequence text,IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {synchronized (mLock) {//参数赋值mHandler.obtainMessage(MSG_SHOW_TOAST, args).sendToTarget();}
}//Handler实现:
case MSG_SHOW_TOAST: {args = (SomeArgs) msg.obj;String packageName = (String) args.arg1;IBinder token = (IBinder) args.arg2;CharSequence text = (CharSequence) args.arg3;IBinder windowToken = (IBinder) args.arg4;ITransientNotificationCallback callback =(ITransientNotificationCallback) args.arg5;int uid = args.argi1;int duration = args.argi2;for (Callbacks callbacks : mCallbacks) {callbacks.showToast(uid, packageName, token, text, windowToken, duration,callback);}break;
}

这里虽然通过遍历Callbacks集合回调,但所有的Callbacks中只有一处真正实现了showToast()接口,就是SystemUI中的ToastUI
ToastUI在启动时即会将自身注册至CommandQueue中,至此便到了Toast显示的最后一步
ToastPresenter

##ToastUI.java##@Override
public void start() {mCommandQueue.addCallback(this);
}@Override
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {if (mPresenter != null) {hideCurrentToast();}Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);View view = ToastPresenter.getTextToastView(context, text);mCallback = callback;mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,packageName);mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
}

ToastPresenter为android系统提供的API,show方法中会进行layoutParams调整,同时移除之前;最终调用WMS的addView添加窗口,完成Toast的显示
从createLayoutParams()方法中,可以看出Toast视图的统一层级为:WindowManager.LayoutParams.TYPE_TOAST;
PS:推测google通过ToastPresenter来统一Toast的显示,规范层级使用

##ToastPresenter.java##/*** Shows the toast in {@code view} with the parameters passed and callback {@code callback}.*/
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,int xOffset, int yOffset, float horizontalMargin, float verticalMargin,@Nullable ITransientNotificationCallback callback) {checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");mView = view;mToken = token;//根据Toast参数重新调整默认layoutParamsadjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,horizontalMargin, verticalMargin);if (mView.getParent() != null) {mWindowManager.removeView(mView);}try {//WMS添加窗口mWindowManager.addView(mView, mParams);} catch (WindowManager.BadTokenException e) {// Since the notification manager service cancels the token right after it notifies us// to cancel the toast there is an inherent race and we may attempt to add a window// after the token has been invalidated. Let us hedge against that.Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);return;}trySendAccessibilityEvent(mView, mPackageName);if (callback != null) {try {callback.onToastShown();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);}}
}/*** Creates {@link WindowManager.LayoutParams} with default values for toasts.*/
private WindowManager.LayoutParams createLayoutParams() {WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = R.style.Animation_Toast;//指定视图层级params.type = WindowManager.LayoutParams.TYPE_TOAST;params.setFitInsetsIgnoringVisibility(true);params.setTitle(WINDOW_TITLE);params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;setShowForAllUsersIfApplicable(params, mPackageName);return params;
}

至此,默认文本类的TextToastRecord流程结束

(2)自定义Toast:CustomToastRecord;此处的callback是否有点熟悉?对,没错,就是之前提到的自定义Toast会创建一个用于与NMS交互的binder - Toast.TN

##CustomToastRecord.java##public final ITransientNotification callback;
@Override
public boolean show() {if (DBG) {Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);}try {callback.show(windowToken);return true;} catch (RemoteException e) {Slog.w(TAG, "Object died trying to show custom toast " + token + " in package "+ pkg);mNotificationManager.keepProcessAliveForToastIfNeeded(pid);return false;}
}

Toast.TN,show()方法也是通过Handler调度;一路看下去可发现;最终也是通过ToastPresenter进行最终的显示

##Toast.java##private static class TN extends ITransientNotification.Stub {@Override@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)public void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(SHOW, windowToken).sendToTarget();}
}
//Handler调度
case SHOW: {IBinder token = (IBinder) msg.obj;handleShow(token);break;
}
//show业务实现
public void handleShow(IBinder windowToken) {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);// If a cancel/hide is pending - no need to show - at this point// the window token is already invalid and no need to do any work.if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {return;}if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,mHorizontalMargin, mVerticalMargin,new CallbackBinder(getCallbacks(), mHandler));}
}

显示流程小总结:

默认文本类Toast:(分别跨越了app,system_server,systemui三个进程)
Toast -> NMS -> TextToastRecord -> StatusBarManagerService -> CommandQueue -> ToastUI -> ToastPresenter

自定义类Toast:(跨越了app,system_server两个进程)
Toast -> NMS -> CustomToastRecord -> Toast.TN -> ToastPresenter
至此,Toast已经走完了显示的所有流程,正常显示在了屏幕上;如何退出的呢?

延迟退出计时

  1. 回到第4点中所讲的;record.show()流程无异常都会返回true,进行后续流程显示;延迟退出则通过scheduleDurationReachedLocked(record)
##NotificationManagerService.java##@GuardedBy("mToastQueue")
void showNextToastLocked() {ToastRecord record = mToastQueue.get(0);while (record != null) {if (record.show()) {scheduleDurationReachedLocked(record);return;}int index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;}
}

延迟的业务即通过Handler发送延迟消息,此处我们在Toast中设置的时长就会排上用场;
默认情况下:Toast.LENGTH_LONG - 3.5s,Toast.LENGTH_SHORT - 2s;
当然,此时长也会受其他因素的影响,即:无障碍辅助功能;注释也很清楚,如下;
辅助功能用户可能需要更长的超时时间。该api将原始延迟与用户的偏好进行比较,并返回较长的延迟。如果没有偏好,则返回原始延迟。
对应会在Settings中存在设置项:本地模拟器Pixel(API_30),设置-无障碍-等待操作的时长 可进行时长或默认设置

##NotificationManagerService.java##//PhoneWindowManager中此处定义为3.5s
static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
static final int SHORT_DELAY = 2000; // 2 secondsprivate void scheduleDurationReachedLocked(ToastRecord r)
{mHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);//默认时长计算int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;//无障碍功能对delay的影响// Accessibility users may need longer timeout duration. This api compares original delay// with user's preference and return longer one. It returns original delay if there's no// preference.delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,AccessibilityManager.FLAG_CONTENT_TEXT);mHandler.sendMessageDelayed(m, delay);
}

退出cancel()

  1. 延迟计时结束后;最后就来到的Toast的退出;延迟消息到达走如下方法
##NotificationManagerService.java##private void handleDurationReached(ToastRecord record)
{if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " token=" + record.token);synchronized (mToastQueue) {int index = indexOfToastLocked(record.pkg, record.token);if (index >= 0) {cancelToastLocked(index);}}
}

cancelToastLocked()具体业务如下;获取对应的ToastRecord对象,调用hide()方法;最后判断,Toast队列非空则重新开始调度显示下一个Toast

##NotificationManagerService.java##void cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);record.hide();ToastRecord lastToast = mToastQueue.remove(index);mWindowManagerInternal.removeWindowToken(lastToast.windowToken, false /* removeWindows */,lastToast.displayId);// We passed 'false' for 'removeWindows' so that the client has time to stop// rendering (as hide above is a one-way message), otherwise we could crash// a client which was actively using a surface made from the token. However// we need to schedule a timeout to make sure the token is eventually killed// one way or another.scheduleKillTokenTimeout(lastToast);keepProcessAliveForToastIfNeededLocked(record.pid);if (mToastQueue.size() > 0) {// Show the next one. If the callback fails, this will remove// it from the list, so don't assume that the list hasn't changed// after this point.showNextToastLocked();}
}
  1. ToastRecord的hide()方法与show()方法流程大致相同,此处不再过多赘述,最终都是到ToastPresenter中,通过WMS接口立即移除视图
##ToastPresenter.java##public void hide(@Nullable ITransientNotificationCallback callback) {checkState(mView != null, "No toast to hide.");if (mView.getParent() != null) {mWindowManager.removeViewImmediate(mView);}try {mNotificationManager.finishToken(mPackageName, mToken);} catch (RemoteException e) {Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);}if (callback != null) {try {callback.onToastHidden();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);}}mView = null;mToken = null;
}
  1. Toast的显示/隐藏回调;接口如下,CallbackBinder为binder对象,把收到的toast显示状态传递到Callback给到外部使用者;那CallbackBinder的状态又是哪里传递的呢?在ToastPresenter中
##Toast.java##public abstract static class Callback {/*** Called when the toast is displayed on the screen.*/public void onToastShown() {}/*** Called when the toast is hidden.*/public void onToastHidden() {}
}private static class CallbackBinder extends ITransientNotificationCallback.Stub {@Overridepublic void onToastShown() {mHandler.post(() -> {for (Callback callback : getCallbacks()) {callback.onToastShown();}});}@Overridepublic void onToastHidden() {mHandler.post(() -> {for (Callback callback : getCallbacks()) {callback.onToastHidden();}});}
}

分别在show与hide时进行状态回调;不同在于:
默认类Toast的最终显示是在SystemUI进程的ToastPresenter,因此回调需要跨进程
自定义类Toast的最终显示是在App进程(准确来讲是调用Toast.show()方法的进程)Toast.TN的ToastPresenter中,因此回调不会涉及跨进程
感兴趣的话可以自行看下这两种CallbackBinder的创建与注入即可。

##ToastPresenter.java##public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,int xOffset, int yOffset, float horizontalMargin, float verticalMargin,@Nullable ITransientNotificationCallback callback) {//..if (callback != null) {try {callback.onToastShown();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);}}
}public void hide(@Nullable ITransientNotificationCallback callback) {//..if (callback != null) {try {callback.onToastHidden();} catch (RemoteException e) {Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);}}//..
}

时序图

附上时序图

到这里Toast流程分析便告一段落,over

一文读懂Toast显示流程相关推荐

  1. 你真的懂数据分析吗?一文读懂数据分析的流程、基本方法和实践

    导读:无论你的工作内容是什么,掌握一定的数据分析能力,都可以帮你更好的认识世界,更好的提升工作效率.数据分析除了包含传统意义上的统计分析之外,也包含寻找有效特征.进行机器学习建模的过程,以及探索数据价 ...

  2. 一文读懂babel编译流程,再也不怕面试官的刁难了

    前言 Babel 是一个强大的 js 编译器.有了 Babel, 我们可以放肆的使用 js 的新特性,而不用考虑浏览器兼容性问题.不仅如此,基于 babel 体系,我们可以通过插件的方法修改一些语法, ...

  3. ac3165 linux驱动_一文读懂Linux系统启动流程

    Linux启动管理 11.1 CentOS 6.x系统启动过程详解 CentOS 6.x系统启动过程发生了较大的变化,使用Upstart启动服务取代了原先的System V init启动服务.Upst ...

  4. 一文读懂华为LTC流程和变革精髓

    所有企业都希望通过重新打通流程,甚至是进行流程化组织建设来推倒部门墙.大家的认识都很统一,但最终大部分企业都雷声大雨点小:一是难有组织保障,跨部门的流程建设需要强大的组织资源和执行力:二是思想不足思路 ...

  5. 一文读懂GBA认证流程

    GBA(Generic Bootstrapping Architecture)是一种用户认证机制,目前多用于运营商相关业务.在数据相关业务中,客户端与应用服务器(NAF)交互过程时,进行用户认证鉴权. ...

  6. hdfs读写流程_一文读懂HDFS分布式存储框架分析

    一文读懂HDFS分布式存储框架分析 HDFS是一套基于区块链技术的个人的数据存储系统,利用无处不在的私人PC存储空间及便捷的网络为个人提供数据加密存储服务,将闲置的存储空间利用起来,服务于正处于爆发期 ...

  7. 一文读懂 Shiro 登录认证全流程

    一文读懂 Shiro 登录认证全流程 登录入口 执行登录 UserRealm Apache Shiro 是 Java 的一个安全框架.Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与 We ...

  8. 一文读懂NPORT(惯导)测试流程

    一文读懂NPORT(惯导)测试流程 1.工具准备: 2.软件准备: 3.线路连接图: 4.测试流程 1.工具准备: NPORT一个,电脑一台,网线一条,U转串一条,电源线两根,电源(12V) 2.软件 ...

  9. 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

    1.引言 我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ.微信.淘宝.那么,一个大型互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂? ...

最新文章

  1. 一个大型网游需要哪些代码块_你会因为网游的非公平性,而转投单机游戏阵营吗?...
  2. 【⭐C/C++の深入浅出⭐】int数与多枚举值互转
  3. VTK:相互作用之RubberBand2DObserver
  4. C和指针之动态内存分配堆、栈、全局区(静态区)、常量区对比总结学习笔记
  5. ckeditor 框架分析 几个核心“人物”
  6. texstudio如何安装cjk宏包_MikTex+TexStudio配置论文写作环境
  7. Updating Homebrew... ...长时间卡住的问题
  8. linux 开发板模拟u盘,ARM-Linux开发 - USB Gadget Storage 制作模拟U盘
  9. FreeBSD学习笔记17-FreeBSD下安装MySQL数据库
  10. 工业自动化要学python_工业的自动化控制除了PLC等常规的以外,是否需要学习相关编程语言,如运动控制,数据采集,机器学习等需要学习什么编程语言...
  11. Tkinter教程之Label篇
  12. 数据分析与爬虫实战视频——学习笔记(四)(糗事百科、天善智能、当当商城、sql输出)
  13. 关闭微信内置浏览器页面
  14. 不在被虐中成长就在被虐中死亡
  15. java界面的面板重绘
  16. 冯·诺依曼体系结构的学习总结
  17. elasticsearch报错:failed send ping to zen unicast
  18. Cadence Allegro导出Gerber步骤
  19. 中国广告灯箱市场现状调查与投资可行性分析报告2022-2028年
  20. 云之家OA系统排名?云之家OA办公系统怎么选?什么是用户口碑最好的云之家OA系统?

热门文章

  1. 【Codecs系列】之视频编码中的块效应、振铃效应和呼吸效应分析
  2. python http的请求和响应
  3. Qt--玩转Excel
  4. ubuntu18.04+RTX3080+pytorch nightly深度学习环境配置
  5. 如何在Word中插入代码片段
  6. Python项目:索引的运用
  7. AUTOCAD中判断点在多段线内部的办法
  8. JVM内存模型及常见问题
  9. 疑难技术点汇总(一)---手机号正则匹配
  10. 大神口中的服务器负载均衡到底是什么意思?