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

相关文章

Vite + Vue3 +Vant4出现Toast is not a function

今天写前端的时候出现了这个问题搞了我一会 搜集原因: 1:是vant版本的问题,Toast()的方法是vant3版本的写法,而我用的是vant4,vant4中的写法改成了showToast()方法,改正过来 import {showToast} from "vant";  发现还是报错,说是找不到对应的样式文件 2:Vant 从 4.0 版本开始不再支持 babel-plugin-i

锁屏界面无法无法显示Toast

在锁屏界面开发几个快捷方式启动某些应用,例如:Calculator, Camera,Google voice和Call等,界面如下。且这种做法很普遍,很多厂商都有。 说明: 1>左边图片右下角为快速启动Calculator的快捷方式,在无需解锁的情况下,进入Calcaulator应用。 2>右边图片为从launcher中启动Calculator后,操作时弹出的Toast提示。 那么问

后台应用服务弹自定义Toast框

之前看到自己的手机应用突然会弹出一个提示框感到有点神奇,不知道是怎么做到的,后面才了解到是后台服务弹的。后台服务弹Toast框其实跟Activity界面差不多,下面先来看看项目SimpleJarService结构: ├── AndroidManifest.xml ├── Android.mk ├── libs │  ├── android-support-v4.jar │  ├── f

自定义Toast完全解析

Toast一般用来显示一行文字,用法比较固定: Toast.makeText(Context context,String message,int duration); 但是有时候想用toast 来显示复杂的view甚至是带有图片的view时这时候就要用到自定义的Toast,自定义Toast主要用到一下几个方法如图: 1.setView()方法用来显示用户自定义的view. 2.

自定义Toast工具类ToastUtil防止多次点击时Toast不消失

有时候我们点击一个按钮出现toast但是当不小心多次点击时,toast会重复出现,这时候通过下面的ToastUtil类可以实现不小心多次点击的问题。 public class ToastUtil {/* private Context context;public ToastUtil(Context context) {this.context=context;}*/private stati

Log,Toast,SPUtil,Density,SDCard,ScreenUtil,AppVersion,KeyBoard,NetWork,HttpUtil工具类

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38965311,本文出自【张鸿洋的博客】 最近统一整理下工具类,以下是栏目,慢慢的就会越来越丰富 http://blog.csdn.net/u013210620/article/category/6251289 1、LogUtil package com.exampl

【M2Det】编译Cython版本NMS

具体参考来自于https://github.com/MrGF/py-faster-rcnn-windows 由于编译gpu版本比较麻烦,所以需要将gpu部分注释掉,只编译cpu即可(GPU版本可以根据本文章顶部链接自行修改) 进入到M2Det/utils目录下,将该目录下的build.py修改为如下形式: # --------------------------------------

在Activity中使用Toast

在Activity中使用Toast Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间,我们现在就尝试一下如何在活动中使用Toast。 首先需要定义一个弹出Toast的触发点,正好界面上有个按钮,那我们就让点击这个按钮的时候弹出一个Toast吧。在onCreate()方法中添加如下代码

Toast实现原理分析

Toast 是 Android 中用于短暂显示提示信息的一种 UI 组件。它通常用于向用户展示一些简短的信息,例如操作结果或状态更新。下面我将结合源码来分析 Toast 的实现原理。 1. Toast 类定义 Toast 类定义如下: 1public class Toast {2 // ...3 private static final String TAG = "Toast

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

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