android WMS服务

android WMS服务

WMS的定义

窗口的分类

WMS的启动

WindowManager

Activity、Window、DecorView、ViewRootImpl 之间的关系

WindowToken


WMS的定义

WMS是WindowManagerService的简称,它是android系统的核心服务之一,它在android的显示功能中扮演着极为重要的角色。一般来说,WMS具有以下四个重要的功能:

  • 窗口管理:负责响应进程的添加、移除窗口、启动窗口的业务,以及管理窗口的坐标、层级、大小、令牌等属性。
  • 窗口动画:负责处理窗口切换时的动画效果。
  • 事件处理:负责处理系统按键、触摸事件给合适的窗口去处理,以及处理部分输入法的交互逻辑。
  • Surface管理:为所有window分配合适的surface,并将排序后的surface交给SurfaceFlinger做进一步的显示工作。

窗口的分类

应用窗口,层级:1~99

子窗口,层级:1000~1999

系统窗口,层级:2000~2999

其中应用窗口层级最低,范围在1~99,系统窗口层级最高,2000~2999,层级越高,意味着越靠近用户,高层级的窗口会覆盖底层级的窗口。

WMS的启动

WMS和AMS,PKMS一样,都是由SystemServer进程启动的,我们看一下代码:

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {...wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);ServiceManager.addService(Context.INPUT_SERVICE, inputManager,/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);t.traceEnd();t.traceBegin("SetWindowManagerService");mActivityManagerService.setWindowManager(wm);t.traceEnd();t.traceBegin("WindowManagerServiceOnInitReady");wm.onInitReady();t.traceEnd();...
}

通过上面代码可知,通过main方法启动服务,然后注册到ServiceManager中,

我们继续跟踪到main方法:

public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm) {return main(context, im, showBootMsgs, onlyCore, policy, atm,SurfaceControl.Transaction::new, Surface::new, SurfaceControl.Builder::new);
}@VisibleForTesting
public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {DisplayThread.getHandler().runWithScissors(() ->sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);return sInstance;
}

可以看到main方法中就new了一个WindowManagerService对象并返回出去。

WindowManager

Window 是一个抽象类,代表一个窗口,其具体的实现类为 PhoneWindow ,它对 View进行管理。
WindowManager 是一个接口类,继承自接口 ViewManager,它是用来管理 Window 的。它的具体实现类为 WindowManagerImpI。
WindowManagerGlobal 是实际操作的类,是一个单例,每个进程中只有一个实例对象,该实例对象在 WindowManagerGlobal 中。
在 WindowManagerGlobal 中,会创建 ViewRootImpl 实例对象,每个根 View 对应一个 ViewRootImpl 实例对象。
想要对 Window (View)进行添加、更新和删除操作,可以使用 WindowManager 来执行。最终的操作是通过 Binder 交给 WMS 来执行的。

我们正常需要添加一个view的视图:

// 获取 WindowManager 
WindowManager wm = (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE); 
// 获取需要添加的View 
View view = View.inflate(MainActivity.this, R.layout.item, null); 
WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 
// 设置不拦截焦点 
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 
params.width = (int) (60 * getResources().getDisplayMetrics().density); 
params.height = (int) (60 * getResources().getDisplayMetrics().density); 
// 且设置坐标系 左上角 
params.gravity = Gravity.LEFT | Gravity.TOP; 
params.format = PixelFormat.TRANSPARENT; 
int width = wm.getDefaultDisplay().getWidth(); 
int height = wm.getDefaultDisplay().getHeight(); 
params.y = height / 2 - params.height / 2; 
wm.addView(view, params);

其实我们调用的就是WindowManagerImpl中的addview,我们跟踪一下,看看这个创建窗口的过程是怎么发送给WMS的。

WindowManagerImpl:

 
public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());}
}

WindowManagerGlobal


ArrayList<View> mViews = new ArrayList<View>();
ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {// ...省略代码...// 1.根节点的LayoutParams必须为WindowManager.LayoutParams类型,因为确定根View的大小需要使用。if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) {// 2.如果这个窗口有父窗口,则需要调整 wparams 的大小,使 wparams 的大小不超过父容器的大小。parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// ...省略代码...}ViewRootImpl root;View panelParentView = null;synchronized (mLock) {// ...省略代码...// 3.将传入的根View添加到ViewRootImpl对象中(一个根View 对应一个 ViewRootImpl)。root = new ViewRootImpl(view.getContext(), display);// 4.将调整后的 wparams 赋值给根 View。view.setLayoutParams(wparams);// 5.将根 View、根View对应的ViewRootImpl、根View的布局参数LayoutParams分别存入三个集合中。mViews.add(view);mRoots.add(root);mParams.add(wparams);try {// 6.执行 ViewRootImpl.setView() 方法。root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// ...省略代码...}}
}

ViewRootImpl

// 用于远程通信的Binder
IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {// ...省略代码...// 1.调用requestLayout方法进行绘制。requestLayout();try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();// 2.获取远程服务进行通信(IWindowSession对象的获取在第4部分分析)res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,mTempInsets);setFrame(mTmpFrame);} catch (RemoteException e) {// ...省略代码...}// ...省略代码...}}
}public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;// 该方法的分析过程请看本文 “scheduleTraversals() 执行流程” 部分。scheduleTraversals();}
}

从代码中我们可以看到,在 setView() 方法中,会调用 mWindowSession.addToDisplayAsUser() 来与远程服务进行通信 (mWindowSession 是一个 Binder 对象),我们继续看:

Session

public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, Rect outFrame,Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outFrame,outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,outInsetsState, outActiveControls, userId);}

WindowManagerService

// WindowManagerService.class
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) {int[] appOp = new int[1];// 1.mPolicy其实是PhoneWindowManager,根据Window的属性来检测权限。int res = mPolicy.checkAddPermission(attrs, appOp);// 没有权限就直接返回。if (res != WindowManagerGlobal.ADD_OKAY) {return res;}...synchronized (mGlobalLock) {.../** 2.通过 displayId 来获得窗口要添加到哪个 DisplayContent 上,如果没有找到DisplayContent,*  则返回 WindowManagerGlobal.ADD_INVALID_DISPLAY 这一状态,其中 DisplayContent 用来描述一块屏幕。*/final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);if (displayContent == null) {return WindowManagerGlobal.ADD_INVALID_DISPLAY;}...// 3. 1000 =< type <= 1999,则该Window属于 SubWindow。if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {parentWindow = windowForClientLocked(null, attrs.token, false);// ...省略2个条件判断代码(1.依附的parentWindow不能为空;2.parentWindow类型也不能是子窗口类型)...// 依附的parentWindow不能为空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;}}...AppWindowToken atoken = null;final boolean hasParent = parentWindow != null;// 4.获取 WindowTokenWindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);// If this is a child window, we want to apply the same type checking rules as the// parent window type.final int rootType = hasParent ? parentWindow.mAttrs.type : type;...if (token == null) {...// 5.没有获取到WindowToken就自己创建一个。token = new WindowToken(this, binder, type, false, displayContent,session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {// 应用程序的窗口类型,就将WindowToken转换为AppWindowToken类型。atoken = token.asAppWindowToken();...}...// 7.每个WindowState 都代表一个窗口。final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid,session.mCanAddInternalSystemWindow);...final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();// 8.根据窗口的 type 对窗口的 LayoutParams 的一些参数进行修改。displayPolicy.adjustWindowParamsLw(win, win.mAttrs, Binder.getCallingPid(), Binder.getCallingUid());win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));// 9.准备将窗口添加到系统中res = displayPolicy.prepareAddWindowLw(win, attrs);...win.attach();// 10.将 WindowState 添加到 mWindowMap 中mWindowMap.put(client.asBinder(), win);...// 11.将 WindowState 添加到该 WindowState 对应的 WindowToken 中。win.mToken.addWindow(win);...}...return res;
}

通过binder完成了和服务端的通信。addview主要做了这么几件事:

addWindow 方怯主要做了下面 4 件事 :

  • 对添加的窗口进行检查,如果窗口不满足条件,就结束添加逻辑。
  • WindowToken 相关的处理,比如有的窗口类型需要提供 WindowToken ,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由 WMS 隐式创建认 WindowToken。
  • WindowState 的创建和相关处理,将 WindowToken 和 WindowState 相关联 。
  • 创建和配置 DisplayContent,完成窗口添加到系统前的准备工作 。

Activity、Window、DecorView、ViewRootImpl 之间的关系

我们看一下activity的UI视图结构:

Activity

Activity 只负责生命周期的控制和事件的处理,并不负责视图控制,真正控制视图的是 Window。
一个 Activity 包含了一个Window,Window 才是真正代表一个窗口,它用于绘制用户的UI界面

Window

Window 是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。
Window 是一个抽象类,实际在 Activity 中持有的是其子类 PhoneWindow。

PhoneWindow  

PhoneWindow 中有个内部类 DecorView,通过创建 DecorView 来加载 Activity.setContentView() 设置的 layout 布局。
Window 通过 WindowManager 将 DecorView 加载其中,并将 DecorView 交给 ViewRootImpl,进行视图绘制以及其他交互。

DecorView

DecorView 是所有应用窗口的根节点, 是 FrameLayout 的子类,它可以被认为是 Android 视图树的根视图。

ViewRootImpl

连接 DecorView 和 WindowManagerService 的纽带。
View 的三大流程 (measure、layout、draw) 和事件分发等都是通过 ViewRootImpl 来执行的。

源码分析

// Activity
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) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);// 1.创建了PhoneWindow对象,在Activity中持有了Window。mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(this);// 2.将 Activity 作为参数传递给 Window,所以在 Window 中持有了 Activity 。mWindow.setCallback(this);// ...省略代码...// 3.设置 WindowManager,来关联 Window 和 DecorView。mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);// 4.在 Activity 中持有 WindowManager。mWindowManager = mWindow.getWindowManager();// ...省略代码...
}

在Activity中,主要做了以下几件事:

  1. 创建一个 PhoneWindow,使 Activity 持有 Window。
  2. 将 Activity作为参数传递给 Window,此时 Window 与 Activity 就相互有了关联。
  3. 给 Window 设置一个 WindowManager,来关联 Window 和 DecorView。
  4. 通过 Window.getWindowManager()获取 WindowManager,使 Activity 持有 WindowManager 的引用。

继续执行onCreate方法:

// Activity 
public void setContentView(@LayoutRes int layoutResID) {// getWindow() 其实就是PhoneWindow,所以这里会触发 PhoneWindow.setContentView()方法。getWindow().setContentView(layoutResID);initWindowDecorActionBar(); //创建ActionBar
}// PhoneWindow.class
public void setContentView(int layoutResID) {if (mContentParent == null) {// 1.mContentParent为空,创建一个DecroView。// mContentParent 其实就是DecroView中id=com.android.internal.R.id.content的容器控件。installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// 2.mContentParent不为空,删除其中的View。mContentParent.removeAllViews();}// 3.将 layoutResID 布局文件加载并添加到 mContentParent 容器控件中。mLayoutInflater.inflate(layoutResID, mContentParent);// ...省略代码...
}// PhoneWindow.class
private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {// 1.创建DecorViewmDecor = generateDecor(-1); // ...省略代码...} else {// 这里会将当前 Window 传入 DecorView,使 DecorView 与 Window 关联。mDecor.setWindow(this);}if (mContentParent == null) {// 2.为DecorView设置布局格式,并返回mContentParentmContentParent = generateLayout(mDecor);// ...省略代码...}
}protected DecorView generateDecor(int featureId) {// ...省略代码...// 创建一个DecorView 根视图 View。return new DecorView(context, featureId, this, getAttributes());
}// PhoneWindow.class
protected ViewGroup generateLayout(DecorView decor) {// 从主题文件中获取样式信息TypedArray a = getWindowStyle();// 1.根据样式信息设置Feature特性// ...省略代码...// 2.根据不同的features加载不同的layout文件// Inflate the window decor.int layoutResource;int features = getLocalFeatures();if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {layoutResource = R.layout.screen_swipe_dismiss;setCloseOnSwipeEnabled(true);}// ...省略代码(条件判断获取layoutResource)...// 3.加载上面的 layoutResource 文件 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 4.获取 DecorView 中的 id=ID_ANDROID_CONTENT 的容器控件。// ID_ANDROID_CONTENT = com.android.internal.R.id.content;ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// ...省略代码...return contentParent;
}// DecorView.class
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {// ...省略代码...// 加载 layoutResource 文件final View root = inflater.inflate(layoutResource, null);// ...省略代码...mContentRoot = (ViewGroup) root;initializeElevation();
}

这里将 Window 作为参数传入 DecorView,使 DecorView 与 Window 关联。

然后在ActivityThread. handleResumeActivity中

// ActivityThread.class
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {// ...省略代码...// TODO Push resumeArgs into the activity for consideration// 1.将 Activity 回复到 RESUME 状态。final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);// ...省略代码...final Activity a = r.activity;// ...省略代码...if (r.window == null && !a.mFinished && willBeVisible) {// 2.获取在 Activity.attach() 方法中就创建了 PhoneWindow 对象。r.window = r.activity.getWindow();View decor = r.window.getDecorView();// 这里使 Decor 不可见。decor.setVisibility(View.INVISIBLE);// 3.获取 Activity 中持有的 WindowManager。ViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;l.softInputMode |= forwardBit;if (r.mPreserveWindow) {a.mWindowAdded = true;r.mPreserveWindow = false;ViewRootImpl impl = decor.getViewRootImpl();if (impl != null) {impl.notifyChildRebuilt();}}if (a.mVisibleFromClient) {if (!a.mWindowAdded) {//将 Activity.WindowAdded 标记为true,避免在 Activity.makeVisible() 是重复进行 Window 添加操作。a.mWindowAdded = true;// 4.将根 View(DecorView)通过 WindowManager 添加到 Window 中。wm.addView(decor, l);} else {a.onWindowAttributesChanged(l);}}}// ...省略代码...// The window is now visible if it has been added, we are not// simply finishing, and we are not starting another activity.if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {// ...省略代码...r.activity.mVisibleFromServer = true;mNumVisibleActivities++;if (r.activity.mVisibleFromClient) {// 5.这个方法内部会是 DecorView 可见。r.activity.makeVisible();}}
}public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, String reason) {// 1.每一个 ActivityClientRecord 都代表着一个 Activity 。final ActivityClientRecord r = mActivities.get(token);// ...省略代码...try {r.activity.onStateNotSaved();r.activity.mFragments.noteStateNotSaved();checkAndBlockForNetworkAccess();if (r.pendingIntents != null) {// 这里会触发 Activity.onNewIntent()方法。deliverNewIntents(r, r.pendingIntents);r.pendingIntents = null;}if (r.pendingResults != null) {// 这里会触发 Activity.onActivityResult()方法。deliverResults(r, r.pendingResults, reason);r.pendingResults = null;}// 这里会触发 Activity.onResume()方法。r.activity.performResume(r.startsNotResumed, reason);r.state = null;r.persistentState = null;// 这里将当前 Activity 的生命周期状态设置为 ON_RESUME。r.setState(ON_RESUME);reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");} catch (Exception e) {// ...省略代码...}return r;
}// Activity.class
void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}// 将 DecorView设置为可见。mDecor.setVisibility(View.VISIBLE);
}

在rusume生命周期中,主要做了以下这些事

  • 获取 DecorView 对象,并设置 DecorView 可见性为不可见。
  • 获取 Activity 中持有的 WindowManager。
  • 将 DecorView 通过 WindowManager 添加到 Window 中显示。
  • 在 Activity.makeVisible() 方法中,最终将 DecorView 设置为可见。

WindowToken

WindowToken是窗口令牌,是一种特殊的Binder令牌,WMS用它唯一的标识系统中的一个窗口。

class WindowToken extends WindowContainer<WindowState> { ... // The actual token. final IBinder token; 
} 

我们通过源码可知,这个windowToken里有一个IBinder对象token,这个token控制着界面显示,这就是为什么Dialog不能使用Application的Context,我们来分析一下。

在Activity的OnCreate创建一个Dialog:

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。如果我们使用Application传入会怎么样呢?

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?

首先我们看到报错是在ViewRootImpl.setView,我们看这个地方关于token的判断:

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: // code1 throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?");     ... } ... } ... 
} 

我们可以快速看出在code 1的地方抛出了异常,是根据一个变量res来判别的,这个res出自方法addToDisplayAsUser, 那么token的判别肯定在这个方法里面了,res只是一个判别的结果,那么我们是必须进入这个addToDisplayAsUser里去看一下,根据上面源码,继续追踪到WindowManagerService.addView:

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; } ...//各种验证 } ... 
}

从代码中可以得出,当token==null的时候,会进行各种判断,第一个返回的就是 WindowManagerGlobal.ADD_BAD_APP_TOKEN ,这样我们就快速找到token的类型:WindowToken。那 么根据我们这一路跟过来,最终找到token的类型了。

我们回到刚才的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 方法中了,我们进去代码看看:

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与Application的WindowManager 首先我们看到Activity的window创建流程。这里需要了解Activity的启动流程。跟踪Activity的启动流程,最终会到 ActivityThread的performLaunchActivity:

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最终会给到哪里,我们赶紧继续看下去:

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的过程。

我们再回过头看一下Dialog的show方法:

public void show() { ... WindowManager.LayoutParams l = mWindow.getAttributes(); ... WindowManager mWindowManager =(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);mWindowManager.addView(mDecor, l); ... 
}

dialog在调用show方法时,首先会获取一个WindowManager对象,然后通过WindowManager的addView方法,将dialog的PhoneWindow中的decorView添加到窗口中。获取这个WindowManager的时候,是通过一个context获取的,这里的context可能是 Activity,也可能是Application,他们的getSystemService返回的windowManager是不一样的,看代码:

//Activity.class
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
public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); 
}

Activity返回的其实是自己的WindowManager,而Application是执行ContextImpl的方法,返回的是应用服务 windowManager。

当我们使用Activity来弹出dialog的时候,此时Activity的DecorView已经是显示到屏幕上了,也就是我们的Activity是有界面了,这个情况下,它就是属于子窗口的类型被添加到PhoneWindow中,而它的token就是DecorView的 token,此时DecorView已经被显示到屏幕上,它本身是拥有token的;

而如果是第一次显示,也就是应用界面,那么他的token就是Activity初始化传入的token。 但是如果使用的是Application,因为它内部并没有token,那么这里获取到的token等于null,后面到WMS也就会抛出异常了。而这也就使用Activity可以弹出Dialog而Application不可以的原因,因为受到了token的限制。

总结:

  1. token在创建ActivityRecord的时候一起被创建,他是一个IBinder对象,实现了接口IApplicationToken。
  2. token创建后会发送到WMS,在WMS中封装成WindowToken,并存在一个 HashMap。 
  3. token会随着ActivityRecord被发送到本地进程,ActivityThread根据AMS的指令执行Activity启动逻辑。
  4. Activity启动的过程中会创建PhoneWindow和对应的WindowManager,同时把token存在PhoneWindow中。
  5. 通过Activity的WindowManager添加view,弹出dialog时会把PhoneWindow中的token放在窗口LayoutParams 中。
  6. 通过viewRootImpl向WMS进行验证,WMS在LayoutParams拿到IBinder之后就可以在Map中获取 WindowToken。
  7. 根据获取的结果就可以判断该token的合法情况。

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

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

相关文章

python基础——异常捕获【try-except、else、finally】

&#x1f4dd;前言&#xff1a; 这篇文章主要介绍一下python基础中的异常处理&#xff1a; 1&#xff0c;异常 2&#xff0c;异常的捕获 3&#xff0c;finally语句 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C语言入门基础以及python入门…

github配置ssh

生成公钥 在电脑用户的目录下打开终端执行 ssh-keygen -t rsa: 执行完不要关 配置文件 看看用户的目录里 .ssh 目录&#xff1a; Host github.comHostname ssh.github.comPort 443配置公钥 复制 id_rsa.pub 文件里的内容 粘贴到 github上 连接密钥 回到刚才的终端…

牛客NC30 缺失的第一个正整数【simple map Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/50ec6a5b0e4e45348544348278cdcee5 核心 Map参考答案Java import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可…

AcWing刷题-区间合并

校门外的树 区间合并&#xff1a; from typing import List def merge(intervals: List[List[int]]) -> List[List[int]]:# 按照第一个元素从小到大进行排序intervals.sort(keylambda x: x[0])# 初始化一个新的数组new_list list()for i in intervals:# 把第一个数组元素添…

Dockerfile:自定义镜像

Dockerfile 是一个文本文件&#xff0c;其中包含了一系列用于自动化构建Docker镜像的指令。通过编写Dockerfile&#xff0c;开发者能够明确地定义一个软件应用及其运行环境应该如何被封装进一个可移植、可重复构建的Docker镜像中。 第一步&#xff1a;在/tmp文件下新建docker…

阿里云效CICD流水线提交前后端项目

后端 一、新建流水线 1进入流水线 2新建流水线 3选择流水线模板 二、上传后端项目 1 将后端项目发布至代码仓库后&#xff0c;在流水线中选择流水线源 我们在选择流水线源之后会出现扫描失败的情况 查看日志发现是因为我们的项目是多模块项目&#xff0c;再扫描的时候无法在…

Android MediaRecorder

AndroidManifest.xml中添加权限标记 <uses-permission android:name"android.permission.RECORD_AUDIO"/> 动态添加权限MainActivity requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO},100); 创建MediaReco…

深度学习基础模型之Mamba

Mamba模型简介 问题&#xff1a;许多亚二次时间架构(运行时间复杂度低于O(n^2)&#xff0c;但高于O(n)的情况)&#xff08;例如线性注意力、门控卷积和循环模型以及结构化状态空间模型&#xff08;SSM&#xff09;&#xff09;已被开发出来&#xff0c;以解决 Transformer 在长…

centos2anolis

我的centos7原地升级到anolis7记录 注意&#xff1a;如果是桌面版请先卸载firefox&#xff0c;否则so文件冲突。 参考&#xff1a; CentOS 7和8Linux系统迁移到国产Linux龙蜥Anolis OS 8手册_disable pam_pkcs11 module in pam configuration-CSDN博客 关于 CentOS 迁移龙蜥…

Intellij IDEA 类注释模板设置

1、配置全局USER 在此配置全局USER&#xff0c;用于填充自动生成的注释中的作者author属性。 注释模板中的user参数是默认是获取系统的用户&#xff08;当然注释作者也可以直接写固定值&#xff09;&#xff0c;如果不想和系统用户用同一个信息&#xff0c;可以在IDEA中进行配…

【42 可视化大屏 | 某瓣电影Top250数据分析可视化大屏】

文章目录 &#x1f3f3;️‍&#x1f308; 1 普版大屏&#x1f3f3;️‍&#x1f308;2 Flask版大屏&#x1f3f3;️‍&#x1f308;3 FlaskMysql版大屏&#x1f3f3;️‍&#x1f308; 4. 可视化项目源码数据 大家好&#xff0c;我是 &#x1f449;【Python当打之年(点击跳转)…

快速上手Spring Cloud 十一:微服务架构下的安全与权限管理

快速上手Spring Cloud 一&#xff1a;Spring Cloud 简介 快速上手Spring Cloud 二&#xff1a;核心组件解析 快速上手Spring Cloud 三&#xff1a;API网关深入探索与实战应用 快速上手Spring Cloud 四&#xff1a;微服务治理与安全 快速上手Spring Cloud 五&#xff1a;Spring …

Flink SQL 基于Update流出现空值无法过滤问题

问题背景 问题描述 基于Flink-CDC &#xff0c;Flink SQL的实时计算作业在运行一段时间后&#xff0c;突然发现插入数据库的计算结果发生部分主键属性发生失败&#xff0c;导致后续计算结果无法插入&#xff0c; 超过失败次数失败的情况问题报错 Caused by: java.sql.BatchUp…

(南京观海微电子)——GOA介绍

GOA是Gate on Array的简写&#xff0c;简单可以理解为gate IC集成在玻璃上了&#xff0c;面板就可以不用gate ic了&#xff0c;是一种低成本的设计&#xff0c;窄边框面板大多数都用了GOA技术。还有一些公司叫GIP&#xff08;Gate in Panel&#xff09;&#xff0c;GDM等等。 …

软考数据库

目录 分值分布1. 事务管理1.1 事物的基本概念1.2 数据库的并发控制1.2.1 事务调度概念1.2.2 并发操作带来的问题1.2.3 并发控制技术1.2.4 隔离级别&#xff1a; 1.3 数据库的备份和恢复1.3.1 故障种类1.3.2 备份方法1.3.3 日志文件1.3.4 恢复 SQL语言发权限收权限视图触发器创建…

AJAX(二):axios 和 fetch函数发送AJAX请求、同源策略、 jsonp、CORS

一、各种发送AJAX请求 jquery基于回调函数&#xff0c;axios基于promise 1.axios发送AJAX请求!!! axios (v1.5.0) - Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 Node.js 中。 | BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务 服务器&#xff1a; app.…

python-pytorch获取FashionMNIST实际图片标签数据集

在查看pytorch官方文档的时候&#xff0c;在这里链接中https://pytorch.org/tutorials/beginner/basics/data_tutorial.html的Creating a Custom Dataset for your files章节&#xff0c;有提到要自定义数据集&#xff0c;需要用到实际的图片和标签。 在网上找了半天没找到&a…

K8S命令行可视化实验

以下为K8s命令行可视化工具的实验内容&#xff0c;相比于直接使用命令行&#xff0c;可视化工具可能更直观、更易于操作。 Lens Lens是用于监控和调试的K8S IDE。可以在Windows、Linux以及Mac桌面上完美运行。在 Kubernetes 上&#xff1a; 托管地址&#xff1a;github/lensa…

物联网学习1、什么是 MQTT?

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎&#xff0c;能够实现传感器、执行器和其它设备之间的高效通…

3D数据格式导出工具HOOPS Publish如何生成高质量3D PDF?

在当今数字化时代&#xff0c;从建筑设计到制造业&#xff0c;从医学领域到电子游戏开发&#xff0c;3D技术已经成为了不可或缺的一部分。在这个进程中&#xff0c;将3D模型导出为3D PDF格式具有重要的意义。同时&#xff0c;HOOPS Publish作为一个领先的解决方案&#xff0c;为…