NMS Toast

2024-04-25 03:18
文章标签 toast nms

本文主要是介绍NMS Toast,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0x00 NMS Toast

Toast.makeText(Context, "Toast message content.", Toast.LENGTH_SHORT).show();

以下代码分析基于Android 8.1.0

0x01 Toast

Toast类只有500多行,逻辑比较简单,主要有三部分组成: Toast,INotificationManager和TN。Toast类负责构造Toast对象;NotificationManager 负责与 NotificationManagerService交互;TN负责Toast最终的显示。

首先构建一个Toast对象:

public static Toast makeText(@NonNull Context context, @Nullable Looper looper,@NonNull CharSequence text, @Duration int duration) {// 创建一个Toast对象Toast result = new Toast(context, looper);// 加载布局,设置Toast要显示的内容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);// 将加载的View设给Toastresult.mNextView = v;result.mDuration = duration;return result;
}

调用Toast.show()方法:

public void show() {// mNextView为最终要展示的View,不能为null,Toast创建的时候默认创建一个,也可通过Toast.setView(View view)设置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 {// 将TN发给NotificationManagerServiceservice.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}
}

几行代码,获取 INotificationManager 服务。

// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
// =======================================================================================private static INotificationManager sService;static private INotificationManager getService() {if (sService != null) {return sService;}sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));return sService;
}

看到 INotificationManager.Stub.asInterface() 很自然的会去搜 NotificationManagerService,嗯嗯,AOSP
中全局搜索就可以了。至此,Toast的工作做完一半,下来的工作进入 NotificationManagerService。

0x02 NotificationManagerService

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

我们来看enqueueToast():

...
final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();
...
private final IBinder mService = new INotificationManager.Stub() {// Toasts// ============================================================================@Overridepublic void enqueueToast(String pkg, ITransientNotification callback, int duration){if (DBG) {Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback+ " duration=" + duration);}if (pkg == null || callback == null) {Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);return ;}final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));final boolean isPackageSuspended =isPackageSuspendedForUser(pkg, Binder.getCallingUid());if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&(!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())|| isPackageSuspended)) {Slog.e(TAG, "Suppressing toast from package " + pkg+ (isPackageSuspended? " due to package suspended by administrator.": " by user request."));return;}synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index;// All packages aside from the android package can enqueue one toast at a timeif (!isSystemToast) {index = indexOfToastPackageLocked(pkg);} else {index = indexOfToastLocked(pkg, callback);}// If the package already has a toast, we update its toast// in the queue, we don't move it to the end of the queue.if (index >= 0) {// Toast已经存在record = mToastQueue.get(index);record.update(duration);record.update(callback);} else {// Toast不存在Binder token = new Binder();// 将这个token添加到系统,否则无法正常显示mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);record = new ToastRecord(callingPid, pkg, callback, duration, token);// 添加到队列,接下来我们去跟踪这个队列的出口mToastQueue.add(record);index = mToastQueue.size() - 1;}keepProcessAliveIfNeededLocked(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);}}}@GuardedBy("mToastQueue")void showNextToastLocked() {// 获取要展示的ToastToastRecord record = mToastQueue.get(0);while (record != null) {if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);try {// 显示Toast,这个callback是上面的TN对象,可以看到,这个时候又调回了Toast.TN.show(IBinder windowToken)record.callback.show(record.token);// 处理Toast显示时间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 dieint index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}keepProcessAliveIfNeededLocked(record.pid);if (mToastQueue.size() > 0) {record = mToastQueue.get(0);} else {record = null;}}}}@GuardedBy("mToastQueue")private void scheduleTimeoutLocked(ToastRecord r){// 从这里可以看到,是依赖Handler实现的mHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);// 这里定义Toast的显示时间,LONG:3.5s, SHORT:2s。注意区分显示时的hideTimeoutMillisecondslong delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;mHandler.sendMessageDelayed(m, delay);}
...
};

0x03 Toast.TN

再回到Toast里,这次看里面的TN,从show(IBinder windowToken)开始。

private static class TN extends ITransientNotification.Stub {private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();private static final int SHOW = 0;private static final int HIDE = 1;private static final int CANCEL = 2;final Handler mHandler;int mGravity;int mX, mY;float mHorizontalMargin;float mVerticalMargin;View mView;View mNextView;int mDuration;WindowManager mWM;String mPackageName;// 默认显示时间static final long SHORT_DURATION_TIMEOUT = 4000; static final long LONG_DURATION_TIMEOUT = 7000;TN(String packageName, @Nullable Looper looper) {// XXX This should be changed to use a Dialog, with a Theme.Toast// defined that sets up the layout params appropriately.final WindowManager.LayoutParams params = mParams;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = com.android.internal.R.style.Animation_Toast;params.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;mPackageName = packageName;// 子线程显示Toast就会抛这个异常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) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SHOW: {// (2) 拿到token,展示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;}}}};}/*** (1) schedule handleShow into the right thread*/@Overridepublic void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);// 发送显示Toast的MessagemHandler.obtainMessage(SHOW, windowToken).sendToTarget();}/*** schedule handleHide into the right thread*/@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.obtainMessage(HIDE).sendToTarget();}/*** hide 和 cancel逻辑相同*/public void cancel() {if (localLOGV) Log.v(TAG, "CANCEL: " + this);mHandler.obtainMessage(CANCEL).sendToTarget();}/*** (3)真正展示Toast的地方,最终还是WindowManager.addView()* @param windowToken 展示Toast的token*/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;}// Toast没有显示过或者当前不再显示状态if (mView != mNextView) {// remove the old view if necessary// 移除之前的ViewhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}// 世界的本源mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);// We can resolve the Gravity here by using the Locale for getting// the layout direction// 以下配置Toast的显示参数final Configuration config = mView.getContext().getResources().getConfiguration();final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());mParams.gravity = gravity;if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {mParams.horizontalWeight = 1.0f;}if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {mParams.verticalWeight = 1.0f;}mParams.x = mX;mParams.y = mY;mParams.verticalMargin = mVerticalMargin;mParams.horizontalMargin = mHorizontalMargin;mParams.packageName = packageName;// 显示超时时间mParams.hideTimeoutMilliseconds = mDuration ==Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;// 要显示,token是必须的mParams.token = windowToken;// 这种方法可以判断当前View是否正在显示,如果显示就remove掉if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);// 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.try {// 好了,真正的开始显示了mWM.addView(mView, mParams);trySendAccessibilityEvent();} catch (WindowManager.BadTokenException e) {/* ignore */}}}private void trySendAccessibilityEvent() {AccessibilityManager accessibilityManager =AccessibilityManager.getInstance(mView.getContext());if (!accessibilityManager.isEnabled()) {return;}// treat toasts as notifications since they are used to// announce a transient piece of information to the userAccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);event.setClassName(getClass().getName());event.setPackageName(mView.getContext().getPackageName());mView.dispatchPopulateAccessibilityEvent(event);accessibilityManager.sendAccessibilityEvent(event);}/*** hide逻辑比较简单,直接remove掉就可以了*/public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {// note: checking parent() just to make sure the view has// been added...  i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeViewImmediate(mView);}// 隐藏后mView被置空mView = null;}}
}

至此,分析完毕。

0x03 疑问

  1. Toast为什么设计成一个远程调用?
  2. Toast和Notification什么关系?

这两个问题需要站在系统的角度考虑。简单谈谈我的理解。Toast和Notification都是系统提供的展示通知的基础组件,所有的APP都可以调用。所有的APP都可以调用,那系统就需要有一定的控制权,不然可能会被滥用。有些ROM显示Toast是需要系统授权的从侧面印证了这点。

0x04 答疑

  • Toast能改显示时间吗?

不可以,Toast的显示时间由系统决定,使用者只能从Toast.LENGTH_SHORT(4s)/Toast.LENGTH_LONG(7s)两种方式中选择一种。

  • 能不能改显示动画?

不可以,Toast.getWindowParams()为hide方法,不可以直接调用。

  • 创建Toast时对传入的Context有要求吗?

这个context有两个作用,inflate出Toast要展示的内容和获取包名等信息。这两个操作对Context都无特殊要求,Application,Activity,Service,BroadcastReceiver的Context都可以。

  • 如果我有两个屏幕,可以在第二个屏幕上显示Toast吗?

显示Toast的Token在创建时使用的DEFAULT_DISPLAY,因此不可以显示在第二块屏幕上。

0xFF 参考

  1. https://blog.csdn.net/lmj623565791/article/details/40481055

这篇关于NMS Toast的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/933578

相关文章

(超详细)YOLOV7改进-Soft-NMS(支持多种IoU变种选择)

1.在until/general.py文件最后加上下面代码 2.在general.py里面找到这代码,修改这两个地方 3.之后直接运行即可

【Rust日报】 2019-05-27:toast - 支持在docker容器中运行任务的工具

Rust中文社区翻译小组招募 #activity #RustChina 首批任务:Rust官网翻译 这里有详细说明 https://github.com/rust-lang/www.rust-lang.org/blob/master/TRANSLATIONS.md 欢迎加入Rust翻译小组参与,QQ群聊号码:716348056 ,QQ群是一个联络点,后续会组建微信群、tg群等。 (有参与

在非anctivity类中使用Toast

最近在做android的项目,在子线程中访问网络资源,当网络访问出现异常时想用Toast提示用户。看了一下makeText方法的参数,第一个就是Context,好的,写个局部变量,提供setter()、getter()方法,把主线程的Context传过来,执行,报错了: 04-09 21:14:44.412 7398-7544/com.explame.cuttese.circle3 E/An

目标检测:NMS代码

非极大值抑制NMS是目标检测常用的后处理算法,用于剔除冗余检测框 总体概要: 对NMS进行分类,大致可分为以下六种,这里是依据它们在各自论文中的核心论点进行分类,这些算法可以同时属于多种类别。 分类优先:传统NMS,Soft-NMS (ICCV 2017) 定位优先:IoU-Guided NMS (ECCV 2018) 加权平均:Weighted NMS (ICME Worksho

Android中的Toast控件

Toast控件的效果如下图:(那个弹出的黑底白字的小长条) 如果要使用系统默认的 Toast 样式,那么要创建一个 Toast 可以这样添加代码: Toast toast = Toast.makeText(getApplicationContext(), "我是toast", Toast.LENGTH_SHORT);toast.show(); 如果想要更改 Toa

官方自带的Toast

uses   Androidapi.JNI.Widget; procedure TFormMain.Button1Click(Sender: TObject); begin  TJToast.JavaClass.makeText(                             SharedActivityContext,

【Vue】toast 轻提示

https://vant-contrib.gitee.io/vant/v2/#/zh-CN/toast 注册安装 import { Toast } from 'vant';Vue.use(Toast) 两种使用方式 导入调用 ( 组件内 或 非组件中均可 ) 比如main.js中也可以调用 import { Toast } from 'vant';Toast('提示内容

Android项目实战--手机卫士30--读取应用的权限以及自定义Toast

最新实战教程,让你了解Android自动化刷量、作弊与防作弊的那些事,案例:刷友盟统计、批量注册苹果帐号 昨天呢,我们就已经把杀死进程的功能给完成的啦,那么今天我们就来做这样一件事,那就是,当我们长按某一个条目的时候,我们就显示出这个条目对应的那个应用的权限 这个做起来就有点难度啦,因为Android没有公开这个api给我们,所以我们就要自己想办法的啦 但是,

VUE:vue中使用toast

新建JS /*** 自定义 提示框( Toast )组件*/var Toast = {};var showToast = false, // 存储toast显示状态showLoad = false, // 存储loading显示状态toastVM = null, // 存储toast vmloadNode = null; // 存储loading节点元素Toast.install = fun

Toast还能显示图片你知道么?

磨砺营IT教育 2016-11-24 11:32 【威哥说】今天就是感恩节了哟,你近期有联系你的家人,你的朋友吗,以及你一切想说感谢的人了吗?定一个闹钟,在你今天上班可以休息的时间,记得给他们发一个QQ留言,或者打一通电话。说一句感谢。 直接上代码: public class TinyjoyToastUtil extends Toast { private static boole