很高兴碰见你~ 欢迎阅读个人文章java
这篇文章讲解关于window token的问题,同时也是Context机制和Window机制这两篇文章的一个补充。若是你对Android的Window机制和Context机制目前位了解过,强烈建议你先阅读前面两篇文章,能够帮助理解整个源码的解析过程以及对token的理解。同时文章涉及到Activty启动流程源码,读者可先阅读Activity启动流程这篇文章。文章涉及到这些方面的内容默认读者已经阅读且了解,不会对这方面的内容过多阐述,若是遇到一些内容不理解,能够找到对应的文章看一下。那么,咱们开始吧。android
当咱们想要在屏幕上展现一个Dialog的时候,咱们可能会在Activity的onCreate方法里这么写:git
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dialog = AlertDialog.Builder(this) dialog.run{ title = "我是标题" setMessage("我是内容") } dialog.show() }
他的构造参数须要一个context对象,可是这个context不能是ApplicationContext等其余context,只能是ActivityContext(固然没有ApplicationContext这个类,也没有ActivityContext这个类,这里这样写只是为了方便区分context类型,下同)。这样的代码运行时没问题的,若是咱们使用Application传入会怎么样呢?api
override fun onCreate(savedInstanceState: Bundle?) { ... // 注意这里换成了ApplicationContext val dialog = AlertDialog.Builder(applicationContext) ... }
运行一下:缓存
报错了,缘由是You need to use a Theme.AppCompat theme (or descendant) with this activity.
,那咱们给他添加一个Theme:session
override fun onCreate(savedInstanceState: Bundle?) { ... // 注意这里添加了主题 val dialog = AlertDialog.Builder(applicationContext,R.style.AppTheme) ... }
好了再次运行:架构
嗯嗯?又崩溃了,缘由是:Unable to add window -- token null is not valid; is your activity running?
token为null?这个token是什么?为何一样是context,使用activity没问题,用ApplicationContext就出问题了?他们之间有什么区别?那么这篇文章就围绕这个token来展开讨论一下。app
文章采用思考问题的思路来展开讲述,我会根据我学习这部份内容时候的思考历程进行复盘。但愿这种解决问题的思惟能够帮助到你。
对token有必定了解的读者能够看到最后部分的总体流程把握,再选择想阅读的部分仔细阅读。框架
首先咱们看到报错是在ViewRootImpl.java:907
,这个地方确定有进行token判断,而后抛出异常,这样咱们就能找到token了,那咱们直接去这个地方看看。:ide
ViewRootImpl.class(api29) public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... int res; ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); ... if (res < WindowManagerGlobal.ADD_OKAY) { ... switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: /* * 1 */ throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); ... } ... } ... }
咱们看到代码就是在注释1的地方抛出了异常,是根据一个变量res
来判断的,这个res
来自方法addToDisplay
,那么token的判断确定在这个方法里面了,res
只是一个 判断的结果,那么咱们须要进到这个addToDisplay
里去看一下。mWindowSession的类型是IWindowSession,他是一个接口,那他的实现类是什么?找不到实现类就没法知道他的具体代码。这里涉及到window机制的相关内容,简单讲一下:
WindowManagerService是系统服务进程,应用进程跟window联系须要经过跨进程通讯:AIDL,这里的IWindowSession只是一个Binder接口,他的具体实现类在系统服务进程的Session类。因此这里的逻辑就跳转到了Session类的
addToDisplay
方法中。关于window机制更加详细的内容,读者能够阅读Android全面解析之Window机制这篇文章进一步了解,限于篇幅这里不过多讲解。
那咱们继续到Session的方法中看一下:
Session.class(api29) class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final WindowManagerService mService; public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel, outInsetsState); } }
能够看到,Session确实是继承自接口IWindowSession,由于WMS和Session都是运行在系统进程,因此不须要跨进程通讯,直接调用WMS的方法:
public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) { ... WindowState parentWindow = null; ... // 获取parentWindow parentWindow = windowForClientLocked(null, attrs.token, false); ... final boolean hasParent = parentWindow != null; // 获取token WindowToken token = displayContent.getWindowToken( hasParent ? parentWindow.mAttrs.token : attrs.token); ... // 验证token if (token == null) { if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { Slog.w(TAG_WM, "Attempted to add application window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } ...//各类验证 } ... }
WMS的addWindow方法代码这么多怎么找到关键代码?还记得viewRootImpl在判断res是什么值的状况下抛出异常吗?没错是WindowManagerGlobal.ADD_BAD_APP_TOKEN和WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN
,咱们只须要找到其中一个就能够找到token的判断位置,从代码中能够看到,当token==null的时候,会进行各类判断,第一个返回的就是WindowManagerGlobal.ADD_BAD_APP_TOKEN
,这样咱们就顺利找到token的类型:WindowToken。那么根据咱们这一路跟过来,终于找到token的类型了。再看一下这个类:
class WindowToken extends WindowContainer<WindowState> { ... // The actual token. final IBinder token; }
官方告诉咱们里面的token变量才是真正的token,而这个token是IBinder对象。
好了到这里关于token是什么已经弄清楚了:
- token是一个IBinder对象
- 只有利用token才能成功添加dialog
那么接下来就有更多的问题须要思考了:
首先,咱们解决第一个问题:Dialog在show过程当中是如何拿到token并给到WMS验证的?
咱们知道致使两种context(activity和application)弹出dialiog的不一样结果,缘由在于token的问题。那么在弹出Dialog的过程当中,他是如何拿到context的token并给到WMS验证的?源码内容不少,咱们须要先看一下token是封装在哪一个参数被传输到了WMS,肯定了参数咱们的搜索范围就减少了,咱们回到WMS的代码:
parentWindow = windowForClientLocked(null, attrs.token, false); WindowToken token = displayContent.getWindowToken( hasParent ? parentWindow.mAttrs.token : attrs.token);
咱们能够看到token和一个attrs.token
关系很是密切,而这个attrs从调用栈一路往回走到了viewRootImpl中:
ViewRootImpl.class(api29) public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... }
能够看到这是一个WindowManager.LayoutParams类型的对象。那咱们接下来须要从最开始show()
开始,追踪这个token是如何被获取到的:
Dialog.class(api30) public void show() { ... WindowManager.LayoutParams l = mWindow.getAttributes(); ... mWindowManager.addView(mDecor, l); ... }
这里的mWindow
和mWindowManager
是什么?咱们到Dialog的构造函数一看究竟:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { // 若是context没有主题,须要把context封装成ContextThemeWrapper if (createContextThemeWrapper) { if (themeResId == Resources.ID_NULL) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } // 初始化windowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // 初始化PhoneWindow final Window w = new PhoneWindow(mContext); mWindow = w; ... // 把windowManager和PhoneWindow联系起来 w.setWindowManager(mWindowManager, null, null); ... }
初始化的逻辑咱们看重点就好:首先判断这是否是个有主题的context,若是不是须要设置主题并封装成一个ContextThemeWrapper对象,这也是为何咱们文章一开始使用application可是没有设置主题会抛异常。而后获取windowManager,注意,这里是重点,也是我当初看源码的时候忽略的地方。这里的context多是Activity或者Application,他们的getSystemService
返回的windowManager是同样的吗,看代码:
Activity.class(api29) public Object getSystemService(@ServiceName @NonNull String name) { if (getBaseContext() == null) { throw new IllegalStateException( "System services not available to Activities before onCreate()"); } if (WINDOW_SERVICE.equals(name)) { // 返回的是自身的WindowManager return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); } ContextImpl.class(api29) public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
Activity返回的实际上是自身的WindowManager,而Application是调用ContextImpl的方法,返回的是应用服务windowManager。这两个有什么不一样,咱们暂时不知道,先留意着,再继续把源码看下去寻找答案。咱们回到前面的方法,看到mWindowManager.addView(mDecor, l);
咱们知道一个PhoneWindow对应一个WindowManager,这里使用的WindowManager并非Dialog本身建立的WindowManager,而是参数context的windowManager,也意味着并无使用本身建立的PhoneWindow。Dialog建立PhoneWindow的目的是为了使用DecorView模板,咱们能够看到addView的参数里并非window而只是mDecor。
咱们继续看代码,,同时要注意这个l
参数,最终token就是封装在里面。addView
方法最终会调用到了WindowManagerGlobal
的addView
方法,具体调用流程能够看我文章开头的文章:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } ... ViewRootImpl root; ... root = new ViewRootImpl(view.getContext(), display); ... try { root.setView(view, wparams, panelParentView); } ... }
这里咱们只看WindowManager.LayoutParams参数,parentWindow是与windowManagerPhoneWindow,因此这里确定不是null,进入到adjustLayoutParamsForSubWindow
方法进行调整参数。最后调用ViewRootImpl的setView方法。到这里WindowManager.LayoutParams这个参数依旧没有被设置token,那么最大的可能性就是在adjustLayoutParamsForSubWindow
方法中了,立刻进去看看:
Window.class(api29) void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { CharSequence curTitle = wp.getTitle(); if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { // 子窗口token获取逻辑 if (wp.token == null) { View decor = peekDecorView(); if (decor != null) { wp.token = decor.getWindowToken(); } } ... } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { // 系统窗口token获取逻辑 ... } else { // 应用窗口token获取逻辑 if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } ... } ... }
终于看到了token的赋值了,这里分为三种状况:应用层窗口、子窗口和系统窗口,分别进行token赋值。
应用窗口直接获取的是与WindowManager对应的PhoneWindow的mAppToken,而子窗口是拿到DecorView的token,系统窗口属于比较特殊的窗口,使用Application也能够弹出,可是须要权限,这里不深刻讨论。而这里的关键就是:这个dialog是什么类型的窗口?以及windowManager对应的PhoneWindow中有没有token?
而这个判断跟咱们前面赋值的不一样WindowManagerImpl有直接的关系。那么这里,就必须到Activity和Application建立WindowManager的过程一看究竟了。
首先咱们看到Activity的window建立流程。这里须要对Activity的启动流程有必定的了解,有兴趣的读者能够阅读Activity启动流程。追踪Activity的启动流程,最终会到ActivityThread的performLaunchActivity
:
ActivityThread.class(api29) private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... // 最终会调用这个方法来建立window // 注意r.token参数 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); ... }
这个方法调用了activity的attach方法来初始化window,同时咱们看到参数里有了r.token
这个参数,这个token最终会给到哪里,咱们赶忙继续看下去:
Activity.class(api29) 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) { ... // 建立window mWindow = new PhoneWindow(this, window, activityConfigCallback); ... // 建立windowManager // 注意token参数 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager(); ... }
attach方法里建立了PhoneWindow以及对应的WindowManager,再把建立的windowManager给到activity的mWindowManager属性。咱们看到建立WindowManager的参数里有token,咱们继续看下去:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated; if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
这里利用应用服务的windowManager给Activity建立了WindowManager,同时把token保存在了PhoneWindow内。到这里咱们知道Activity的PhoneWindow是拥有token的。那么Application呢?
Application调用的是ContextImpl的getSystemService方法,而这个方法返回的是应用服务的windowManager,Application自己并无建立本身的PhoneWindow和WindowManager,因此也没有给PhoneWindow赋值token的过程。
所以,Activity拥有本身PhoneWindow以及WindowManager,同时它的PhoneWindow拥有token;而Application并无本身的PhoneWindow,他返回的WindowManager是应用服务windowManager,并无赋值token的过程。
那么到这里结论已经快要出来了,还差最后一步,咱们回到赋值token的那个方法中:
Window.class(api29) void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { // 子窗口token获取逻辑 if (wp.token == null) { View decor = peekDecorView(); if (decor != null) { wp.token = decor.getWindowToken(); } } ... } else { // 应用窗口token获取逻辑 if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } ... } ... }
当咱们使用Activity来添加dialog的时候,此时Activity的DecorView已是添加到屏幕上了,也就是咱们的Activity是有界面了,这个状况下,他就是属于子窗口的类型被添加到PhoneWindow中,而他的token就是DecorView的token,此时DecorView已经被添加到屏幕上,他自己是拥有token的;
这里补充一点。当一个view(view树)被添加到屏幕上后,他所对应的viewRootImpl有一个token对象,这个token来自WindowManagerGlobal,他是一个IWindowSession 对象。从源码中能够看到,当咱们的PhoneWindow的DecorView展现到屏幕后,后续添加的子window的token,就都是这个IWindowSession 对象了。
而若是是第一次添加,也就是应用界面,那么他的token就是Activity初始化传入的token。
可是若是使用的是Application,由于它内部并无token,那么这里获取到的token就是null,后面到WMS也就会抛出异常了。而这也就是为何使用Activity能够弹出Dialog而Application不能够的缘由。由于受到了token的限制。
到这里咱们已经知道。咱们从WMS的token判断找到了token的类型以及token的载体:WindowManager.LayoutParams,而后咱们再从dialog的建立流程追到了赋值token的时候会由于windowManager的不一样而不一样。所以咱们再去查看了二者不一样的windowManager,最终获得结论Activity的PhoneWindow拥有token,而Application使用的是应用级服务windowManager,并无token。
那么此时仍是会有疑问:
虽然到目前咱们已经弄清缘由,可是知识却少了一块,秉着探索知识的好奇心咱们继续研究下去。
咱们从前面Activity的建立window过程知道token来自于r.token
,这个r
是ActivityRecord,是AMS启动Activity的时候传进来的Activity信息。那么要追踪这个token的建立就必须顺着这个r
的传递路线一路回溯。一样这涉及到Activity的完整启动流程,我不会解释详细的调用栈状况,默认你清楚activity的启动流程,若是不清楚,能够先去阅读Activity的启动流程。首先看到这个ActivityRecord是在哪里被建立的:
/frameworks/base/core/java/android/app/servertransaction/LaunchActivityItem.java/; public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mIsForward, mProfilerInfo, client); // ClientTransactionHandler是ActivityThread实现的接口,具体逻辑回到ActivityThread client.handleLaunchActivity(r, pendingActions, null /* customIntent */); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); }
这样咱们须要继续往前回溯,看看这个token是在哪里被获取的:
/frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java public void execute(ClientTransaction transaction) { ... executeCallbacks(transaction); ... } public void executeCallbacks(ClientTransaction transaction) { ... final IBinder token = transaction.getActivityToken(); item.execute(mTransactionHandler, token, mPendingActions); ... }
能够看到咱们的token在ClientTransaction对象获取到。ClientTransaction是AMS传来的一个事务,负责控制activity的启动,里面包含两个item,一个负责执行activity的create工做,一个负责activity的resume工做。那么这里咱们就须要到ClientTransaction的建立过程一看究竟了。下面咱们的逻辑就要进入系统进程了:
ActivityStackSupervisor.class(api28) final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { ... final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread, r.appToken); ... }
这个方法建立了ClientTransaction,可是token并非在这里被建立的,咱们继续往上回溯(注意代码的api版本,不一样版本的代码会不一样):
ActivityStarter.java(api28) private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int realCallingPid, int realCallingUid, int startFlags, SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup) { ... //记录获得的activity信息 ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor, checkedOptions, sourceRecord); ... }
咱们一路回溯,终于看到了ActivityRecord的建立,咱们进去构造方法中看看有没有token相关的构造:
ActivityRecord.class(api28) ActivityRecord(... Intent _intent,...) { appToken = new Token(this, _intent); ... } static class Token extends IApplicationToken.Stub { ... Token(ActivityRecord activity, Intent intent) { weakActivity = new WeakReference<>(activity); name = intent.getComponent().flattenToShortString(); } ... }
能够看到确实这里进行了token建立。而这个token看接口就知道是个Binder对象,他持有ActivityRecord的弱引用,这样能够访问到activity的全部信息。到这里token的建立咱们也找到了。那么WMS是怎么知道一个token是否合法呢?每一个token建立后,会在后续发送到WMS ,WMS对token进行缓存,然后续对于应用发送来的token只须要在缓存拿出来匹配一下就知道是否合法了。那么WMS是怎么拿到token的?
activity的启动流程后续会走到一个方法:startActivityLocked
,这个方法在我前面的activity启动流程并无讲到,由于它并不属于“主线”,可是他有一个很是重要的方法调用,以下:
ActivityStack.class(api28) void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, boolean newTask, boolean keepCurTransition, ActivityOptions options) { ... r.createWindowContainer(); ... }
这个方法就把token送到了WMS 那里,咱们继续看下去:
ActivityRecord.class(api28) void createWindowContainer() { ... // 注意参数有token,这个token就是以前初始化的token mWindowContainerController = new AppWindowContainerController(taskController, appToken, this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges, task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(), appInfo.targetSdkVersion, mRotationAnimationHint, ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L); ... }
注意参数有token,这个token就是以前初始化的token,咱们进入到他的构造方法看一下:
AppWindowContainerController.class(api28) public AppWindowContainerController(TaskWindowContainerController taskController, IApplicationToken token, AppWindowContainerListener listener, int index, int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges, boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable, int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos, WindowManagerService service) { ... synchronized(mWindowMap) { AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder()); ... atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(), inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion, requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, this); ... } }
还记得咱们在一开始看WMS的时候他验证的是什么对象吗?WindowToken,而AppWindowToken是WindowToken的子类。那么咱们继续追下去:
AppWindowContainerController.class(api28) AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, int configChanges, boolean launchTaskBehind, boolean alwaysFocusable, AppWindowContainerController controller) { return new AppWindowToken(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, controller); } AppWindowToken(WindowManagerService service, IApplicationToken token, ...) { this(service, token, voiceInteraction, dc, fullscreen); ... } WindowToken.class WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) { token = _token; ... onDisplayChanged(dc); }
createAppWindow方法调用了AppWindow的构造器,而后再调用了父类WindowToken的构造器,咱们能够看到这里最终对token进行了缓存,并调用了一个方法,咱们看看这个方法作了什么:
WindowToken.class void onDisplayChanged(DisplayContent dc) { dc.reParentWindowToken(this); ... } DisplayContent.class(api28) void reParentWindowToken(WindowToken token) { addWindowToken(token.token, token); } private void addWindowToken(IBinder binder, WindowToken token) { ... mTokenMap.put(binder, token); ... }
mTokenMap 是一个 HashMap<IBinder, WindowToken> 对象,这里就能够保存一开始初始化的token以及后来建立的windowToken二者的关系。这里的逻辑其实已经在WMS中了,因此这个也是保存在WMS中。AMS和WMS都是运行在系统服务进程,于是他们之间能够直接调用方法,不存在跨进程通讯。WMS就能够根据IBinder对象拿到windowToken进行信息比对了。至于怎么比对,代码位置在一开始的时候已经有涉及到,读者可自行去查看源码,这里就不讲了。
那么,到这里关于整个token的知识就所有走了一遍了,AMS怎么建立token,WMS怎么拿到token的流程也根据咱们回溯的思路走了一遍。
前面根据咱们思考问题的思惟走完了整个token流程,可是彷佛仍是有点乱,那么这一部分,就把前面讲的东西整理一下,对token的知识有一个总体上的感知,同时也当时前面内容的总结。先来看总体图:
这就是整个token的运做流程了。而具体的源码和细节在上面已经解释完了,读者可自行选择重点部分再次阅读源码。
我在Context机制一文中讲到,不一样的context拥有不一样的职责,系统对不一样的context限制了不一样的权利,让在对应情景下的组件只能作对应的事情。其中最明显的限制就是UI操做。
token看着是属于window机制的领域内容,实际上是context的知识范畴。咱们知道context一共有三种最终实现类:Activity、Application、Service,context是区分一个类是普通Java类仍是android组件的关键。context拥有访问系统资源的权限,是各类组件访问系统的接口对象。可是,三种context,只有Activity容许有界面,而其余的两种是不能有界面的,也不必有界面。为了防止开发者乱用context形成混乱,那么必须对context的权限进行限制,这也就是token存在的意义。拥有token的context能够建立界面、进行UI操做,而没有token的context如service、Application,是不容许添加view到屏幕上的(这里的view除了系统窗口)。
为何说这不属于window机制的知识范畴?从window机制中咱们知道WMS控制每个window,是经过viewRootImpl中的IWindowSession来进行通讯的,token在这个过程当中只充当了一个验证做用,且当PhoneWindow显示了DecorView以后,后续添加的View使用的token都是ViewRootImpl的IWindowSession对象。这表示当一个PhoneWindow能够显示界面后,那么对于后续其添加的view无需再次进行权限判断。于是,token真正限制的,是context是否能够显示界面,而不是针对window。
而咱们了解完底层逻辑后,不是要去知道怎么绕过他的限制,动一些“大胆的想法”,而是要知道官方这么设计的目的。咱们在开发的时候,也要针对不一样职责的context来执行对应的事务,不要使用Application或Service来作UI操做。
文章采用思考问题的思路来表述,经过源码分析,讲解了关于token的建立、传递、验证等内容。同时,token在源码设计上的思想进行了总结。
android体系中各类机制之间是互相联系,彼此链接构成一个完整的系统框架。token涉及到window机制和context机制,同时对activity的启动流程也要有必定的了解。阅读源码各类机制的源码,能够从多个维度来帮助咱们对一个知识点的理解。同时阅读源码的过程当中,不要局限在当前的模块内,思考不一样机制之间的联系,系统为何要这么设计,解决了什么问题,能够帮助咱们从架构的角度去理解整个android源码设计。阅读源码切忌无目标乱看一波,要有明确的目标、验证什么问题,针对性寻找那一部分的源码,与问题无关的源码暂时忽略,否则会在源码的海洋里游着游着就溺亡了。
全文到此,感谢你的阅读
原创不易,以为有帮助能够点赞收藏评论转发关注。
笔者能力有限,有任何想法欢迎评论区交流指正。
如需转载请私信交流。另外欢迎光临笔者的我的博客:传送门