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。未完待续。。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/582388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解ArkTS:Harmony OS 应用开发语言 TypeScript 的基础语法和关键特性

文章目录 前言TypeScript语法变量声明条件控制循环迭代函数类和接口模块开发文章总结要点总结前言 Harmony OS应用开发的主力语言ArkTS的前身TS语言的基本语法。通过学习变量的声明和数据类型、条件控制、函数声明、循环迭代等基本知识,并了解内核接口的声明和使用。同时还介…

openGauss学习笔记-175 openGauss 数据库运维-备份与恢复-导入数据-管理并发写入操作示例

文章目录 openGauss学习笔记-175 openGauss 数据库运维-备份与恢复-导入数据-管理并发写入操作示例175.1 相同表的INSERT和DELETE并发175.2 相同表的并发INSERT175.3 相同表的并发UPDATE175.4 数据导入和查询的并发 openGauss学习笔记-175 openGauss 数据库运维-备份与恢复-导入…

redis开启密码验证

开启密码验证 &#xff08;1&#xff09;配置文件中设置 redis.conf文件里面配置requirepass参数&#xff0c;redis认证密码&#xff1a;foobared&#xff0c;然后重启redis服务 ./redis-cli 127.0.0.1:6379> 127.0.0.1:6379> 127.0.0.1:6379> CONFIG SET requi…

训练和测试的loss、accuracy等数据保存到文件并读出

首先是写文件到excel import os.path from openpyxl import load_workbook import pandas as pd import matplotlib.pyplot as pltdef write_excel(excel_name, sheet_name, value):columns ["epoc", "train_loss", "train_acc", "test_a…

跟着野火学FreeRTOS:第一段(任务延时列表)

既然是延时列表&#xff0c;那肯定要先定义相应的链表&#xff0c;延时列表的定义如下。这里定义了两条延时列表&#xff08;其实就是前面小节里面提到的链表的根节点&#xff09;&#xff0c;一条是准备当记录 S y s t i c k Systick Systick周期个数的变量 x T i c k C o u n…

idea Spring Boot项目使用JPA创建与数据库链接

1.pom.xml文件中添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>…

创建和配置Spring MVC框架构建Web应用

1 认识Spring MVC Spring Web MVC是构建在Servlet API之上的Web框架&#xff0c;自诞生之时就被纳入了Spring框架中。其正式/官方名称为“Spring Web MVC”&#xff0c;源自其所属的模块&#xff08;spring-webmvc&#xff09;&#xff0c;但通常被称为“Spring MVC”。 1.1…

最短路径(数据结构实训)(难度系数100)

最短路径 描述&#xff1a; 已知一个城市的交通路线&#xff0c;经常要求从某一点出发到各地方的最短路径。例如有如下交通图&#xff1a; 则从A出发到各点的最短路径分别为&#xff1a; B&#xff1a;0 C&#xff1a;10 D&#xff1a;50 E&#xff1a;30 F&#xff1a;60 输…

“暂存”校验逻辑探讨

1、背景 在业务中可能会遇到这种场景&#xff0c;前端页面元素多且复杂&#xff0c;一次性填完提交耗时很长&#xff0c;中间中断面临着丢失数据的风险。针对这个问题&#xff0c;“暂存”应运而生。 那“暂存”的时候&#xff0c;是否需要对数据校验&#xff0c;如何进行校验…

【全局光照GI系统剖析_Enlighten和Progressive Lightmapper_案例分享(附带场景下载链接)_场景】

烘焙预计算 前文:续_直接和间接光照这一篇小结:Unity烘焙预计算烘焙前的场景设置1.2.Contribute GI如下图:物体的Static和面板上的Contribute GILightmap的UV模型自带Lightmap的UVUnity 自动展Lightmap的UV1.3.Meta PassMeta Pass代码如下:1.4.Light Mode模式

k8s部署 CNI 网络组件与k8s集群搭建(二)

目录 部署 CNI 网络组件 部署 flannel K8S 中 Pod 网络通信 Flannel UDP 模式的工作原理 ETCD 之 Flannel 提供说明 Flannel VXLAN 模式跨主机的工作原理 在 node01 节点上操作 在 master01 节点上操作 部署 Calico k8s 组网方案对比 Calico 主要由三个部分组成 Ca…

go 源码解读 - sync.Mutex

sync.Mutex mutex简介mutex 方法源码标志位获取锁LocklockSlowUnlock怎么 调度 goroutineruntime 方法 mutex简介 mutex 是 一种实现互斥的同步原语。&#xff08;go-version 1.21&#xff09; &#xff08;还涉及到Go运行时的内部机制&#xff09;mutex 方法 Lock() 方法用于…

网盘项目话术(0.5w字精选)

功能结构图 数据库设计总结 该项目主要就是对文件的操作&#xff0c;file表&#xff0c;file_share表。 file表主要字段&#xff1a;id&#xff0c;用户id&#xff0c;父级目录id&#xff0c;文件的地址&#xff0c;文件的封面图片地址&#xff0c;创建和修改时间。 file_sha…

Java命令行最实用的命令

Java 命令行提供了许多重要且实用的命令&#xff0c;以下是其中一些常用的命令&#xff1a; java&#xff1a;用于运行 Java 程序的命令。使用 java 命令后面跟着要执行的 Java 类名。 java MyClassjavac&#xff1a;用于编译 Java 源代码文件的命令。使用 javac 命令后面跟着要…

vue中的事件绑定的过程

Vue中事件的绑定分为两种情况&#xff1a;原生DOM事件和组件事件。 原生DOM事件 是指在模板中使用v-on或指令来给元素绑定的事件&#xff0c;如 <button click"handler">click me</button>这种事件的绑定是通过以下步骤实现的&#xff1a; 模板编译阶…

国际物流公司科普_集装箱种类区分和介绍_箱讯科技

集装箱运输的不断发展&#xff0c;为适应装载不同种类货物的需要&#xff0c;因而出现了不同种类的集装箱。今天和大家一起来总结一下。 按使用材料分类 根据箱子主体部件&#xff08;侧壁、端壁、箱顶等&#xff09;采用什么材料&#xff0c;就叫做什么材料制造的集装箱&…

TPRI-DMP平台介绍

TPRI-DMP平台介绍 TPRI-DMP平台概述 TPRI-DMP为华能集团西安热工院自主产权的工业云PaaS平台&#xff0c;已经过13年的发展和迭代&#xff0c;其具备大规模能源电力行业生产应用软件开发和运行能力。提供TPRI-DMP平台主数据管理、业务系统开发与运行、应用资源管理与运维监控…

【数据结构】C语言实现单链表的基本操作

单链表基本操作的实现 导言一、查找操作1.1 按位查找1.1.1 按位查找的C语言实现1.1.2 按位查找的时间复杂度 1.2 按值查找1.2.1 按值查找的C语言实现1.2.2 按值查找的时间复杂度 二、插入操作2.1 后插操作2.2 前插操作 三、删除操作结语 导言 大家好&#xff0c;很高兴又和大家…

C++ 之LeetCode刷题记录(三)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅&#xff0c;多学多练&#xff0c;尽力而为。 先易后难&#xff0c;先刷简单的。 13、罗马数字转整数 罗马数字包含以下七种字符: I&#xff0c…

vue3中新增的组合式API:ref、reactive、toRefs、computed、watch、provide/inject、$ref

在 Vue3 中&#xff0c;组合式 API 是一种新的编程模式&#xff0c;它允许你更灵活地组织和重用代码。组合式 API 主要包括以下几个部分&#xff1a; ref&#xff1a;用于创建响应式数据。reactive&#xff1a;用于创建一个响应式对象。toRefs&#xff1a;将一个响应式对象转换…