为什么要再读Activity的启动呢,因为在启动弹窗的时候,出现了一个这个报错:
1 2 3 4 Process: com.librty.crashsolution, PID: 7037 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? at android.view.ViewRootImpl.setView(ViewRootImpl.java:1189) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:400) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:138)
里面个讲到一个window.token值。由于这个值的不正确导致的。 首先需要找到抛出异常的位置:在ViewRootImpl的setView阶段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class ViewRootImpl { public void setView (View view,WindowManager.LayoutParams attrs,View panelParentView,int userId) { ... int res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, mTempControls); if (res < WindowManagerGlobal.ADD_OKAY) { switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?" ); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application" ); } } ... } }
可以看到res的值是通过mWindowSession.addToDisplayerAsUser方法获取到的。 获取过程如下:第一步:搞明白mWindowSession对象的获取过程
1 2 3 4 5 6 7 8 9 10 class ViewRootImpl { final IWindowSession mWindowSession; public ViewRootImpl (Context context, Display display) { this (context, display, WindowManagerGlobal.getWindowSession(), false ); } public ViewRootImpl (Context cotext,Display display,IWindowSession session,boolean useSfChoreographer) { mWindowSession = sesssion; } }
可以看到mWindowSession是通过WindowManagerGlobal.getWindowSession()方法获取的。可以看到这里是通过Binder通信去获取WindowManagerService服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static IWindowSession getWindowSession () { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null ) { try { @UnsupportedAppUsage InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged (float scale) { ValueAnimator.setDurationScale(scale); } }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } } class WindowManagerService extends IWindowManager .Stub implements Watchdog .Monitor , WindowManagerPolicy .WindowManagerFuncs { public IWindowSession openSession (IWindowSessionCallback callback) { return new Session(this , callback); } }
可以看到iWindowSession其实就是Session对象。
第二步调用addToDisplayAsUser方法
1 2 3 4 5 6 7 8 9 10 class Session { final WindowManagerService mService; public int addToDisplayAsUser (IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this , window, attrs, viewVisibility, displayId, userId, requestedVisibilities, outInputChannel, outInsetsState, outActiveControls); } }
可以看到又调用了WindowManagerService的addWindow方法:
1 2 3 4 5 6 7 8 class WindowManagerService { public int addWindow (Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { } }
这里就引出了很重要的一个服务WindowManagerService
,它管理了当前系统中的所有window窗口,他主管了设备产生的touch事件,因为windowmanagerservice知道当前哪个window最适合处理事件。
来看下WindowmanagerService里面所有的窗口是如何管理的:
现在继续看addWindow整个过程干了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class WindowManagerService { public static final int FIRST_SUB_WINDOW = 1000 ; public static final int FIRST_SYSTEM_WINDOW = 2000 ; public int addWindow (Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { Arrays.fill(outActiveControls, null ); int [] appOp = new int [1 ]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0 ; int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName, appOp); if (res != ADD_OKAY) { return res; } final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token); if (displayContent == null ) { ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does " + "not exist: %d. Aborting." , displayId); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } final int type = attrs.type; if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { parentWindow = windowForClientLocked(null , attrs.token, false ); if (parentWindow == null ) { ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: " + "%s. Aborting." , attrs.token); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) { ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: " + "%s. Aborting." , attrs.token); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (mWindowMap.containsKey(client.asBinder())) { ProtoLog.w(WM_ERROR, "Window %s is already added" , client); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { parentWindow = windowForClientLocked(null , attrs.token, false ); if (parentWindow == null ) { ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: " + "%s. Aborting." , attrs.token); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) { ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: " + "%s. Aborting." , attrs.token); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } } } }
第一步: 进行权限检查。看你这个类型的window能够进行创建。第二步: 获取Display。Display就是屏幕的意思,手机的可显示区域会通过displayId进行标记,每个displayId对应一个display,然后display会被保存在DisplayContent里面。到了这里就要讲到上面的关于token产生的报错问题了。
在图上可以看到通过token值从DisplayContent里面获取WindowToken值。windowToken是一组窗口的编号,比如我们在activity里面弹出了一个dialog,那么这个时候dialog的windowtoken和activity的token的值就是一样的,因为dialog是activity的子窗口,他们属于一组窗口,所以windowtoken的值是一样的。
这个token是activity的。 这个token是activity上弹的dialog弹窗。
从我们的实验结果能发现,属于一组窗口的时候他们的windowToken的值是一样的。什么叫一组窗口,就是存在父子关系的,比如activity和他的dialog。两个activity间就不是一组窗口,因此他们的windowtoken也不一样。
在WindowManagerservice.addWindow()里面会为窗口每次去创建WindowState对象,每个windowSate都保存了windowToken值,所以间接的,通过windowToken管理了一组windowState,是怎么实现的呢? WindowToken继承自WindowContainer,然后在WindowContainer里面有一个WindowList数组,这个数组就存下了相同token下的所有窗口。
可以看到一组相同的windowToken会通过windowList保存所有的windowState。 经过这么一通分析,上面这张图已经搞懂了WindowToken和WidowState的关系了。但是我现在还有一个疑问这个WindowToken的值是从哪来的?继续分析…..
WindowToken
是通过IBinder
值从DisplayContent
获取到的。然后IBinder
值是通过保存在WindowManager.LayoutParams
里的token
值来获取的。那现在就一步步往上追,这个WindowManager.LayoutParams.token的赋值时机:
① 调用者Session:
② 调用者ViewRootImpm.setView
③ 调用者WindowManagerGlobal.addView:
④ 调用者WindowManagerImpl.addView:
⑤ 调用者ActivityThread.handleResumeActivity:
现在分析每步干了什么: ⑤的位置: 首先获取到一个空的LayoutParams,因为在我们每次创建Window对象的时候,都会立马创建一个空的WindManager.LayoutParams对象。然后在⑤位置给我们的window设置了type值WindowManager.LayoutParams.TYPE_BASE_APPLICATION这个常量值是1也就代表这是一个应用窗口。
④的位置: 没干任何事情。直接就掉用了WindowManagerGlobal.addView方法。
③的位置: 对token进行赋值
这里的parentWindow并不为空,因为在Activity的attach阶段,创建了我们的windowManagetImpl对象
现在看回adjustLayoutParamsForSubWindow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Window { 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(); } } if (curTitle == null || curTitle.length() == 0 ) { final StringBuilder title = new StringBuilder(32 ); if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) { title.append("Media" ); } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) { title.append("MediaOvr" ); } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { title.append("Panel" ); } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) { title.append("SubPanel" ); } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) { title.append("AboveSubPanel" ); } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) { title.append("AtchDlg" ); } else { title.append(wp.type); } if (mAppName != null ) { title.append(":" ).append(mAppName); } wp.setTitle(title); } } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { if (curTitle == null || curTitle.length() == 0 ) { final StringBuilder title = new StringBuilder(32 ); title.append("Sys" ).append(wp.type); if (mAppName != null ) { title.append(":" ).append(mAppName); } wp.setTitle(title); } } else { if (wp.token == null ) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } if ((curTitle == null || curTitle.length() == 0 ) && mAppName != null ) { wp.setTitle(mAppName); } } if (wp.packageName == null ) { wp.packageName = mContext.getPackageName(); } if (mHardwareAccelerated || (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0 ) { wp.flags |= FLAG_HARDWARE_ACCELERATED; } } }
到这里就结束了,LayoutParams该设置的值都设置结束了。
在标记1 的地方可以看到WindowManagerService有一个成员变量RootWindowContainer。我们都知道WindowManagerService可以管理所有窗口,这就是答案所在。在RootWindowContainer里面有一个数组专门用来保存创建的窗口。
1 2 3 class RootWindowContainer {protected final WindowList<E> mChildren = new WindowList<E>();}
在标记2 的地方,通过token值从RootWindowContainer里面获取对应存在的DisplayContent。在我们创建一个activity的时候,我可以给你讲,这个token的值是null。token的赋值操作可以通过获取Widow的WindowManager.LayoutParams直接进行赋值的:
1 2 3 4 5 6 val p = WindowManager.LayoutParams() p.flags = WindowManager.LayoutParams.FLAG_SPLIT_TOUCH p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL p.token = applicationContext.getActivityToken() var mWindowManager = getSystemService(WINDOW_SERVICE) as WindowManagermWindowManager.addView(view, p)
标记3 :通过token获取不到值的时候,就通过displayId来获取,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class RootWindowContainer { DisplayContent getDisplayContentOrCreate (int displayId) { DisplayContent displayContent = getDisplayContent(displayId); if (displayContent != null ) { return displayContent; } if (mDisplayManager == null ) { return null ; } final Display display = mDisplayManager.getDisplay(displayId); if (display == null ) { return null ; } displayContent = new DisplayContent(display, this ); addChild(displayContent, POSITION_BOTTOM); return displayContent; } }
其实看到这里我又不懂了,这个displayId是什么,Display又是什么?我没搞懂 (2022年11月24日,今天我搞懂了。)
在android里面display就是一个可以展示的屏幕,displayid就是标记是哪个屏幕。surfaceflinger就是通过displayid将合成的图片分发给指定的屏幕。在我们手机只有一个屏幕的时候这个dispalyid就是0.如果有两个屏幕分别为0 和 1(就是这么个意思,也可能不是0 1。,反正就是两个不同的数字)。有一个屏幕就有一个display对象,有两个屏幕就有两个display对象。
第三步: 当type大于1000,小于1999,说明这个窗口是一个子窗口。如果是子窗口就会在第三步去找到他的父窗口也就是代码里面的parentWindow,activity是应用窗口所以在第三步不会去找他的父窗口。Dialog是一个子窗,这个时候会去找他的父窗口,现在来研究下是怎么找的。
1 2 3 class WindowManagerService { parentWindow = windowForClientLocked(null , attrs.token, false ); }
可以看到在第二步的时候去判断了窗口类型。
看到这里总结一下:在创建ViewRootImpl对象的时候,会去获取WindowManagerService服务去创建Session对象。然后调用session对象的addToDisplayAsUser方法,他的返回值结果用来表示窗口添加结果情况情况。
现在来总结下全部过程,在ActivityThread的performLaunchActivity阶段会去创建Activity对象,并调用Activity的attach方法。在attach里面会去创建PhoneWindow对象。然后给当前创建的PhoneWindow创建对应的WindowManagerImpl对象,通过方法window.setWindowManager()。创建WindowManagerImpl对象。PhoneWindow持有WindowManagerImpl的引用。WindowManagerImpl也持有window的引用。在这个阶段还涉及了一个很重的参数token:IBinder。每个Activity都有一个对应的token:IBinder。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Activtiy { public void attach () { mWindow = PhoneWindow(); mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken,mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED)!=0 ); } } class Window { public void setWindowManager (WindowManager wm,IBinder appToken,String appName,boolean hardwareAccelerated) { mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this ); } } class WindowManagerImpl { public WindowManagerImpl crateLocalWindowManager (Window parentWindow) { return new WindowManagerImpl(mContext,parentWindow,mWindowContextToken); } }
然后在ActivityThread的handleResumeActivity阶段,设置Activity对应的window属性type为应用窗口属性1,然后调用WindowManagerImpl的addView方法。在addView方法又再次调用了WindowManagerGlobal.addView方法。这里需要注意,WindowManagerGlobal对象是一个单例对象,一个进程只会存在一份,然后windowmanagerImpl对象是在每次创建窗口都会新建一个。 接着来到了windowmanagetGlobal.addView方法。在这个时候会进行很重要的一次操作,就是给window设置token:IBinder值。token值用来标记了哪些窗口是一组的,比如activity和他的dialog,他们的token值就是一样的,两个activity的token值就是不一样的。 设置完window的token值,接着创建了ViewRootImpl对象。然后调用了ViewRootImpl的setView方法。这里面干了很重的操作,不仅调用了requestlayout触发了整个view树的measure layout draw操作,还干了件大事就是完成了window窗口的添加。调用方法windowsession.addToDisplayAsUser方法。这里就涉及到了跨进程通信。windowSession是通过跨进程的方式从windowManagerService里面获取到的session对象。接着看addToDisplayToUser方法。这个时候就来到了system_server进程。 这个时候就来到了一个新的领域,android窗口添加过程了。在session调用addDisplayToUser的时候会调用到WindowManagerService.addWindow方法。然后通过displayid或者token值获取对应的displayContent。如果用户手机有多个屏幕的话就会有多个Displaycontent对象。如果只有一个屏幕。那只会有一个DisplayContent对象。通过token从DisplayContent里面获取windowtoken值。然后为窗口创建windowstate对象。通过windowtoken维护着一组windowstate。然后displaycontent维护着一组windowtoken。至此结束。