Android 12 Token 机制
一、前言
在 android framework 框架中 activity 和 window 是相互关联的,而他们的管理者 AMS 和 WMS 是怎么来实现这种关联关系的,答案就是通过 token。
首先大家需要了解一下 LayoutParams,当然属性很多,简单了解即可:
base/core/java/android/view/WindowManager.java
...//窗口类型//有3种主要类型如下://ApplicationWindows取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间,是常用的顶层应用程序窗口,须将token设置成Activity的token;//SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之间,与顶层窗口相关联,需将token设置成它所附着宿主窗口的token;//SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之间,不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用;public int type;//WindowType:开始应用程序窗口public static final int FIRST_APPLICATION_WINDOW = 1;//WindowType:所有程序窗口的base窗口,其他应用程序窗口都显示在它上面public static final int TYPE_BASE_APPLICATION = 1;//WindowType:普通应用程序窗口,token必须设置为Activity的token来指定窗口属于谁public static final int TYPE_APPLICATION = 2;//WindowType:应用程序启动时所显示的窗口,应用自己不要使用这种类型,它被系统用来显示一些信息,直到应用程序可以开启自己的窗口为止public static final int TYPE_APPLICATION_STARTING = 3;//WindowType:结束应用程序窗口public static final int LAST_APPLICATION_WINDOW = 99;//WindowType:SubWindows子窗口,子窗口的Z序和坐标空间都依赖于他们的宿主窗口public static final int FIRST_SUB_WINDOW = 1000;//WindowType: 面板窗口,显示于宿主窗口的上层public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;//WindowType:媒体窗口(例如视频),显示于宿主窗口下层public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;//WindowType:应用程序窗口的子面板,显示于所有面板窗口的上层public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;//WindowType:对话框,类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;//WindowType:媒体信息,显示在媒体层和程序窗口之间,需要实现半透明效果public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;//WindowType:子窗口结束public static final int LAST_SUB_WINDOW = 1999;//WindowType:系统窗口,非应用程序创建public static final int FIRST_SYSTEM_WINDOW = 2000;//WindowType:状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;//WindowType:搜索栏,只能有一个搜索栏,位于屏幕上方public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;//WindowType:电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;//WindowType:系统提示,出现在应用程序窗口之上public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;//WindowType:锁屏窗口public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;//WindowType:信息窗口,用于显示Toastpublic static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//WindowType:系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;//WindowType:电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;//WindowType:系统对话框public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;//WindowType:锁屏时显示的对话框public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;//WindowType:系统内部错误提示,显示于所有内容之上public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;//WindowType:内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;//WindowType:内部输入法对话框,显示于当前输入法窗口之上public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;//WindowType:墙纸窗口public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;//WindowType:状态栏的滑动面板public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;//WindowType:安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;//WindowType:拖放伪窗口,只有一个阻力层(最多),它被放置在所有其他窗口上面public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;//WindowType:状态栏下拉面板public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;//WindowType:鼠标指针public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;//WindowType:导航栏(有别于状态栏时)public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//WindowType:音量级别的覆盖对话框,显示当用户更改系统音量大小public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//WindowType:起机进度框,在一切之上public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;//WindowType:假窗,消费导航栏隐藏时触摸事件public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;//WindowType:梦想(屏保)窗口,略高于键盘public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;//WindowType:导航栏面板(不同于状态栏的导航栏)public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;//WindowType:universe背后真正的窗户public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;//WindowType:显示窗口覆盖,用于模拟辅助显示设备public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;//WindowType:放大窗口覆盖,用于突出显示的放大部分可访问性放大时启用public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;//WindowType:......public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29;public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;//WindowType:系统窗口结束public static final int LAST_SYSTEM_WINDOW = 2999;......}
这里需要我们知道的是 WindowManager.LayoutParams 有三种窗口 type,分别对应为:
- 应用窗口程序:type 值在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW,必须将 token 设置为 Activity 的 token。
- 子窗口: type 值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows,必须将 token 设置为 Activity 的 token。比如 PopupWindow。
- 系统窗口: type 值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW,使用需要权限,属于特定的系统功能。比如 Toast。
这里就说到了 token 的问题,应用窗口程序和子窗口均需要获取到 Activity 的 token。那么 token 是什么呢?
二、Token 是什么?
Token 是 ActivityRecord 的静态内部类,我们先来看下 Token 的继承关系,Token 继承于IApplicationToken.Stub,从 IApplicationToken.Stub 类进行继承,根据 Binder 的机制可以知道 Token 是一个匿名 Binder 实体类,这个匿名 Binder 实体会传递给其他进程,其他进程会拿到 Token 的代理端。
我们知道匿名 Binder 有两个比较重要的用途,一个是拿到 Binder 代理端后可跨 Binder 调用实体端的函数接口,另一个作用便是在多个进程中标识同一个对象。往往这两个作用是同时存在的,比如我们这里研究的 Token 就同时存在这两个作用,但最重要的便是后者,Token 标识了一个 ActivityRecord 对象,即间接标识了一个 Activity。
base/service/core/java/com/android/server/wm/ActivityRecord.java
static class Token extends IApplicationToken.Stub {private WeakReference<ActivityRecord> weakActivity;private final String name;private final String tokenString;Token(Intent intent) {name = intent.getComponent().flattenToShortString();tokenString = "Token{" + Integer.toHexString(System.identityHashCode(this)) + "}";}private void attach(ActivityRecord activity) {if (weakActivity != null) {throw new IllegalStateException("Already attached..." + this);}weakActivity = new WeakReference<>(activity);}private static @Nullable ActivityRecord tokenToActivityRecordLocked(Token token) {if (token == null) {return null;}ActivityRecord r = token.weakActivity.get();if (r == null || r.getRootTask() == null) {return null;}return r;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder(128);sb.append("Token{");sb.append(Integer.toHexString(System.identityHashCode(this)));sb.append(' ');if (weakActivity != null) {sb.append(weakActivity.get());}sb.append('}');return sb.toString();}@Overridepublic String getName() {return name;}}
三、Token 的创建
private ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,@Nullable String _launchedFromFeature, Intent _intent, String _resolvedType,ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo,String _resultWho, int _reqCode, boolean _componentSpecified,boolean _rootVoiceInteraction, ActivityTaskSupervisor supervisor,ActivityOptions options, ActivityRecord sourceRecord, PersistableBundle persistentState,TaskDescription _taskDescription, long _createTime) {super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true,null /* displayContent */, false /* ownerCanManageAppTokens */);mAtmService = _service;appToken = (Token) token;info = aInfo;mUserId = UserHandle.getUserId(info.applicationInfo.uid);packageName = info.applicationInfo.packageName;intent = _intent;。。。。。。
在 ActivityRecord 的构造方法中创建这个 token ,并赋值给成员变量 appToken。标识着当前这个ActivityRecord,即间接代表着一个Activity。
而创建 ActivityRecord 的地方在 ActivityStarter.java 的 executeRequest 方法中,可以参考 Android四大组件系列2 Activity启动流程(上)
ActivityStarter.java
private int executeRequest(Request request) {......final ActivityRecord r = new ActivityRecord.Builder(mService).setCaller(callerApp).setLaunchedFromPid(callingPid).setLaunchedFromUid(callingUid).setLaunchedFromPackage(callingPackage).setLaunchedFromFeature(callingFeatureId).setIntent(intent).setResolvedType(resolvedType).setActivityInfo(aInfo).setConfiguration(mService.getGlobalConfiguration()).setResultTo(resultRecord).setResultWho(resultWho).setRequestCode(requestCode).setComponentSpecified(request.componentSpecified).setRootVoiceInteraction(voiceSession != null).setActivityOptions(checkedOptions).setSourceRecord(sourceRecord).build();......
}
ActivityRecord.java
private ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,@Nullable String _launchedFromFeature, Intent _intent, String _resolvedType,ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo,String _resultWho, int _reqCode, boolean _componentSpecified,boolean _rootVoiceInteraction, ActivityTaskSupervisor supervisor,ActivityOptions options, ActivityRecord sourceRecord, PersistableBundle persistentState,TaskDescription _taskDescription, long _createTime) {super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true,null /* displayContent */, false /* ownerCanManageAppTokens */);// 可知调用父类也就是 WindowToken 的构造方法并new了一个 token 作为参数传入mAtmService = _service;appToken = (Token) token;info = aInfo;mUserId = UserHandle.getUserId(info.applicationInfo.uid);packageName = info.applicationInfo.packageName;intent = _intent;
}
WindowToken.java
protected WindowToken(WindowManagerService service, IBinder _token, int type,boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens) {this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens,false /* roundedCornerOverlay */, false /* fromClientToken */, null /* options */);}protected WindowToken(WindowManagerService service, IBinder _token, int type,boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {super(service);token = _token;windowType = type;mOptions = options;mPersistOnEmpty = persistOnEmpty;mOwnerCanManageAppTokens = ownerCanManageAppTokens;mRoundedCornerOverlay = roundedCornerOverlay;mFromClientToken = fromClientToken;if (dc != null) {dc.addWindowToken(token, this);}}
WindowToken 是 ActivityRecord 的父类,最终赋值给 WindowToken 的 token 变量 。
class WindowToken extends WindowContainer<WindowState> {private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowToken" : TAG_WM;/** The actual token */final IBinder token;/** The type of window this token is for, as per {@link WindowManager.LayoutParams} */final int windowType;......
}
以上 Token 的创建均在 AMS 服务端完成。
四、Token 怎么传递给客户端?
可以参考 Android四大组件系列3 Activity启动流程(下)
ActivityTaskSupervisor.realStartActivityLocked
boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,boolean andResume, boolean checkConfig) throws RemoteException {
......// Create activity launch transaction.final ClientTransaction clientTransaction = ClientTransaction.obtain(proc.getThread(), r.appToken); // 这里的 r.appToken 就是 ActivityRecord 的 token
......// Schedule transaction.mService.getLifecycleManager().scheduleTransaction(clientTransaction);// 回传给客户端进程,执行 ClientTransaction
}
app 端通过 TransactionExecutor 来执行 ClientTransaction
public void execute(ClientTransaction transaction) {if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");final IBinder token = transaction.getActivityToken(); // 获取AMS传递过来的token......
最后执行 LaunchActivityItem 的 execute
LaunchActivityItem.java
public void preExecute(ClientTransactionHandler client, IBinder token) {ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken,mLaunchedFromBubble);// 通过 token 创建 ActivityClientRecord ,至此绑定成功client.addLaunchingActivity(token, r);client.updateProcessState(mProcState, false);client.updatePendingConfiguration(mCurConfig);if (mActivityClientController != null) {ActivityClient.setActivityClientController(mActivityClientController);}}
随后调用 Activity 的 attach 方法,会传入 token
Activity.java
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,IBinder shareableActivityToken) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(mWindowControllerCallback);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);mWindow.getLayoutInflater().setPrivateFactory(this);if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {mWindow.setSoftInputMode(info.softInputMode);}if (info.uiOptions != 0) {mWindow.setUiOptions(info.uiOptions);}mUiThread = Thread.currentThread();mMainThread = aThread;mInstrumentation = instr;mToken = token; // token 保存到 activity 中......mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);// 关键方法,把 mToken 传递给 PhoneWindow 的 mAppTokenmWindowManager = mWindow.getWindowManager(); // window 的WindowManager 赋值给了activity......
通过 mWindow.setWindowManager 把 mToken 传递给 PhoneWindow 的成员变量 mAppToken。
这样就建立了 AMS 中的 ActivityRecord 与 客户端进程 Activity 以及对应的 PhoneWindow 之间的一一对应关系。
我们接下来看下 Window 的 setWindowManager 方法:
Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken; // 赋值 tokenmAppName = appName;mHardwareAccelerated = hardwareAccelerated;if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}
在 setWindowManager 方法中,token 被赋值到 Window 的 mAppToken 属性上,同时在当前 Window 上创建了 WindowManager 。
继续看 WindowManagerImpl 的 createLocalWindowManager,其中把当前 Window 作为参数传入:
WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}private WindowManagerImpl(Context context, Window parentWindow,@Nullable IBinder windowContextToken) {mContext = context;mParentWindow = parentWindow; // 新建的 PhoneWindow 赋值给了 mParentWindowmWindowContextToken = windowContextToken;
}
从以上可知 WindowManagerImpl 的 mParentWindow 参数非空,并且值是新建的 PhoneWindow。
这个非常重要,在下面窗口添加的时候会判断这个变量,然后来决定 token 的赋值操作。
五、通过添加窗口 Token 传递到 WMS
Activity.java
void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}
关注这个 WindowManager ,是通过 getWindowManager 获得的。
Activity.java
public WindowManager getWindowManager() {return mWindowManager;}
我们知道这个 mWindowManager ,其实是 Window 的 WindowManager
Window.java
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
也就是说这个 WindowManagerImpl 的 mParentWindow 不为空,并且就是之前 new 的 PhoneWindow。
接下来继续看窗口的添加代码:
添加窗口的操作在 WindowManagerGlobal 的 addView 方法中如下:
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) { // parentWindow 不为空parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// If there's no parent, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}......
}
由前面的 parentWindow 赋值情况我们知道,对于 Activity 启动流程来说,走到这里,parentWindow一定是不为 null 的。
其实:只有系统窗口,parentWindow 才会为 null。
以上可知 parentWindow 不为空,然后走 Window 的 adjustLayoutParamsForSubWindow 函数确定 token值。
Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {CharSequence curTitle = wp.getTitle();if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {if (wp.token == null) {View decor = peekDecorView();if (decor != null) {wp.token = decor.getWindowToken();// 如果是子窗口,则把父窗口的token赋值}}......} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {......} else {if (wp.token == null) {wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;// 把当前窗口的 mAppToken 赋值给 wp.token}......}if (wp.packageName == null) {wp.packageName = mContext.getPackageName();}if (mHardwareAccelerated ||(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {wp.flags |= FLAG_HARDWARE_ACCELERATED;}}
这里会判断窗口类型,设置 token。获取到 Token 后就保存在了 LayoutParams 里面,可知这个 token 来自窗口的 mAppToken。
之后被传递到 ViewRootImpl.setView 中去。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {...mWindowAttributes.copyFrom(attrs);// 拷贝含有 token 的 attrs 到 mWindowAttributes...res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,mTempControls);// 传递 mWindowAttributes 到 WMS }}
这里将包含 token 的 LayoutParams 通过 Session 最终调用到了 WMS 的 addWindow 方法(这些流程前面的章节都提到过,所以这里就简单带过)。
WindowManagerService.java
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsState requestedVisibility,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {...// 通过 token 获取到 DisplayContent,来判断是否是非法的显示内容final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);......//根据 token 获取 activity,判断所添加的合法性WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);// 通过 displayContent 从 mTokenMap 中获取对应的 WindowToken,第一次获取为空if (token == null) {......final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();token = new WindowToken.Builder(this, binder, type).setDisplayContent(displayContent).setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow).setRoundedCornerOverlay(isRoundedCornerOverlay).build();// 通过这个 token 创建一个新的 WindowToken,// 并会被添加到displayContent的mTokenMap 中......}......final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);// 然后以 WindowToken 创建 WindowState }
综上,简单总结一下,每个 Activity 都有一个自己的 token,用于各种校验,而对于 WMS 来说,如果想添加非系统级别的窗口,都需要一个合理的 token。
接下来我们来分析下其他类型窗口的创建,比如 Dialog。未完待续。。。。。。
相关文章:

Android 12 Token 机制
一、前言 在 android framework 框架中 activity 和 window 是相互关联的,而他们的管理者 AMS 和 WMS 是怎么来实现这种关联关系的,答案就是通过 token。 首先大家需要了解一下 LayoutParams,当然属性很多,简单了解即可…...

TCP与UDP是流式传输协议吗?
TCP(传输控制协议)和UDP(用户数据报协议)是两种主要的传输层协议,它们用于在网络中传输数据。它们不是流式传输协议,而是提供了不同的数据传输特性: 1. TCP(传输控制协议࿰…...

61 贪心算法解救生艇问题
问题描述:第i个人的体重为peaple[i],每个船可以承载的最大重量为limit。每艘船最多可以同时载两人,但条件是这些人的重量之和最多为limit,返回载到每一个人多虚的最小船数,(保证每个人被船载)。 贪心算法求解:先将数组…...

C#高级 01.Net多线程
一.基本概念 1.什么是线程? 线程是操作系统中能独立运行的最小单位,也是程序中能并发执行的一段指令序列线程是进程的一部分,一个进程可以包含多个线程,这些线程共享进程资源进程有线程入口,也可以创建更多的线程 2.…...

Java---泛型讲解
文章目录 1. 泛型类2. 泛型方法3. 泛型接口4. 类型通配符5. 可变参数6. 可变参数的使用 1. 泛型类 1. 格式:修饰符 class 类名 <类型>{ }。例如:public class Generic <T>{ }。 2. 代码块举例: public class Generic <T>{…...

【论文阅读笔记】SegVol: Universal and Interactive Volumetric Medical Image Segmentation
Du Y, Bai F, Huang T, et al. SegVol: Universal and Interactive Volumetric Medical Image Segmentation[J]. arXiv preprint arXiv:2311.13385, 2023.[代码开源] 【论文概述】 本文思路借鉴于自然图像分割领域的SAM,介绍了一种名为SegVol的先进医学图像分割模型…...

Unix/Linux操作系统介绍
1、Unix/Linux操作系统介绍 1.1、操作系统的作用 1)操作系统的目标 方便:使计算机系统易于使用有效:以更有效的方式使用计算机系统资源扩展:方便用户有效开发、测试、引进新功能 2)操作系统的地位 操作系统在计算…...

什么是https证书?
HTTPS证书,也称为SSL(Secure Sockets Layer)证书或TLS(Transport Layer Security)证书,是一种数字证书,用于在网络上建立安全的加密连接。它的主要目的是确保在互联网上进行的数据传输的安全性和…...

C++ DAY2作业
1.课堂struct练习,用class; #include <iostream>using namespace std;class Stu { private:int age;char sex;int high; public:double score;void set_values(int a,char b,int c,double d);int get_age();char get_sex();int get_high(); }; vo…...

RabbitMQ核心概念记录
本文来记录下RabbitMQ核心概念 文章目录 什么叫消息队列为何用消息队列RabbitMQ简介RabbitMQ基本概念RabbitMQ 特点具体特点包括 Rabbitmq的工作过程RabbitMQ集群RabbitMQ 的集群节点包括Rabbit 模式大概分为以下三种单一模式普通模式镜像模式 本文小结 什么叫消息队列 消息&am…...

算法时间空间复杂度计算—空间复杂度
算法时间空间复杂度计算—空间复杂度 空间复杂度定义影响空间复杂度的因素算法在运行过程中临时占用的存储空间讲解 计算方法例子1、空间算法的常数阶2、空间算法的线性阶(递归算法)3、二分查找分析方法一(迭代法)方法二ÿ…...

计算机专业校招常见面试题目总结
博主面试岗位包括:java开发、软件测试、测试开发等岗位,基于之前经历的面试总结出的一些常见题目。仅供参考,互相学习!! 八股:java开发、测试、测开岗位 Java技术栈:Java基础、JVM、数据结构、…...

网络编程『简易TCP网络程序』
🔭个人主页: 北 海 🛜所属专栏: Linux学习之旅、神奇的网络世界 💻操作环境: CentOS 7.6 阿里云远程服务器 文章目录 🌤️前言🌦️正文TCP网络程序1.字符串回响1.1.核心功能1.2.程序…...

java itext5 生成PDF并填充数据导出
java itext5 生成PDF并填充数据导出 依赖**文本勾选框****页眉**,**页脚****图片**实际图 主要功能有文本勾选框,页眉,页脚,图片等功能。肯定没有专业软件画的好看,只是一点儿方法。仅供参考。 依赖 <!--pdf-->&…...

如何配置TLSv1.2版本的ssl
1、tomcat配置TLSv1.2版本的ssl 如下图所示,打开tomcat\conf\server.xml文件,进行如下配置: 注意:需要将申请的tomcat版本的ssl认证文件,如server.jks存放到tomcat\conf\ssl_file\目录下。 <Connector port"1…...

在CentOS 7上使用普通用户`minio`安装和配置MinIO
指定控制台端口号6901 以下是在CentOS 7上使用普通用户minio安装和配置MinIO的完整步骤,包括设置密码、设置开机自启动,以及使用minio用户启动和关闭服务的过程: 创建MinIO用户: sudo useradd -m minio sudo passwd minio这将创建一个可以登录…...

Vue3-27-路由-路径参数的简单使用
什么是路径参数 在路由配置中,可以将【参数】放在【路由路径】中, 从而实现,同一个 路由,同一个组件,因路径参数不同,可以渲染出不同的内容。特点 : 1、当携带不同路径参数的路由相互跳转时&am…...

w7数据库基础之mysql函数
系统函数 1.version() --mysql版本 2.user() --当前登录的数据库用户名system_user() 3.database() --当前使用的数据库名。schema() 4.datadir --数据库路径 5.version_compile_os 操作系统版本,like 后面可以使用%%进行模糊查询。 6.hostname 当前机器…...

智能优化算法应用:基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工蜂鸟算法4.实验参数设定5.算法结果6.…...

Docker的基础使用
Docker的基础使用 Docker 是一个开放平台,用于开发、运输和运行应用程序。Docker 允许你将应用程序与基础架构分离,从而可以像管理应用程序一样快速交付软件。以下是 Docker 的详细使用指南: 安装 Docker 下载 Docker : 根据你的操作系统…...

Sass(Scss)、Less的区别与选择 + 基本使用
在前端开发中,CSS预处理器成为了提高样式表开发效率的重要工具。Sass(以及其语法Scss)和Less是两个最为流行的CSS预处理器,它们在语法、功能和用法上存在一些差异,因此在选择使用时需要考虑多个因素。 1. Sass 和 Les…...

GPT Zero 是什么?
from https://openaigptguide.com/gptzero/ 在人工智能技术飞速发展的今天,人们对于文字内容的准确性和可信度要求越来越高。例如在学术研究领域,防止抄袭和造假是非常重要的。而对于普通用户而言,辨别哪些内容是由人工智能生成的࿰…...

c++学习笔记-提高篇-案例2-员工分组(vector/multimap)
一、案例描述 公司今天招聘10个员工(ABCDEFGHIJ),10名员工进入公司后,需要指派员工在哪个部门工作员工信息:姓名 工资组成;部门分为:策划、美术、研发随机给10名员工分配部门和工作通过multimap进行信息插…...

TrustZone之问答
以下问题有助于测试您的知识。 在Arm架构中,安全状态和物理地址空间分别是什么? 在Arm架构中,安全状态分为安全状态和非安全状态。物理地址空间分为安全物理地址空间和非安全物理地址空间。 在每个异常级别中,是什么确定处理器处于…...

vue3中新增的组合式API:ref、reactive、toRefs、computed、watch、provide/inject、$ref
在 Vue3 中,组合式 API 是一种新的编程模式,它允许你更灵活地组织和重用代码。组合式 API 主要包括以下几个部分: ref:用于创建响应式数据。reactive:用于创建一个响应式对象。toRefs:将一个响应式对象转换…...

Flask 密码重设系统
Flask 密码重设系统【源码来自编程浪子Flask点餐小程序】 web/templates/user/reset_pwd.html {% extends "common/layout_main.html" %} {% block content %} {% include "common/tab_user.html" %} <div class"row m-t user_reset_pwd_wrap&q…...

HarmonyOS4.0开发应用(四)【ArkUI状态管理】
ArkUI状态管理 分为以下四个: StateProp和LinkProvide和ConsumeObserved和ObjectLink State 相当于vue中data()内定义的属性变量,相当于react中useState()的使用,即绑定在视图上的响应式变量,可动态更新~ Tip: 标记的变量必须初始化,不可为空…...

JS常见正则表达式写法(附案例)
正则表达式方法示例: 1. test方法解析,test判断正则是否在字符串中出现过,如果出现返回true,如果没出现返回false。 let str hello world; let ret1 /e/.test(str); // true let ret2 /q/.test(str); // false 如&…...

go语言,ent库与gorm库,插入一条null值的time数据
情景介绍 使用go语言,我需要保存xxxTime的字段至数据库中,这个字段可能为空,也可能是一段时间。我采取的是统一先赋值为空,若有需要,则再进行插入(需要根据另一个字段判断是否插入) 在我的数据…...

Java EasyExcel 导入代码
Java EasyExcel 导入代码 导入方法 /*** 仓库库位导入** param req* param res* param files* throws Exception*/RequestMapping(value {"/import/line_store_locs"}, method {RequestMethod.POST})ResponseBodypublic void importStoreLoc(HttpServletRequest …...