以前写过客户端Window的创建过程,大概是这样子的。我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用ActivityThead中handleLaunchActivity中的handleResumeActivity方法了,最后会调用makeVisible方法,把这个DecorView显示出来。
那么今天所讨论的是话题主要有,创建后的Activity的窗口是怎么添加的,WindowManagerService是如何感知Activity窗口添加的?系统中有很多应用,每个应用有多个Activity,一个Activity对应一个Window,WindowManagerService是怎么管理的?本文基于Android7.0源码。
一、从ActivityThread#handleResumeActivity方法说起
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .... if (r != null) { final Activity a = r.activity; .... //Activity的Window没有被添加过并且Activity没有finish和需要设置成可见 if (r.window == null && !a.mFinished && willBeVisible) { //对Actiivty成员变量window赋值 r.window = r.activity.getWindow(); //获取Window的DecorView View decor = r.window.getDecorView(); //将DecorView设置成可见 decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; ... if (a.mVisibleFromClient && !a.mWindowAdded) { a.mWindowAdded = true; //调用ViewManager的方法添加decor wm.addView(decor, l); } ... }
ViewManager定义 了操作View的三大方法,addView,updateViewLayout,removeView;比如ViewGroup就实现了这个接口。
public interface ViewManager{ /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
但是这个地方,我们要看的ViewManager的实现类是WindowManagerImpl,所以调用的是WindowManagerImpl的addView方法,
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); }
WindowManagerImpl紧接就把这个活交给了WindowManagerGlobal,所以要去看WindowManagerGlobal的addView方法。WindowManagerGlobal是用来管理Window的全局类,它里面维护了几个全局的列表。
//存储所有Window所对应的View
private final ArrayList<View> mViews = new ArrayList<View>(); //存储所有Window对应的ViewRootImpl private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); //存储所有Window对应的布局参数 private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); //存储所有将要被删除的View,即Window private final ArraySet<View> mDyingViews = new ArraySet<View>();
下面是addView方法,很容易懂,主要做了如下几个事情
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { //第一件事情:检查参数是否合法,如果是子Window,还要调整布局参数 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.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent and we're running on L or above (or in the // system context), assume we want hardware acceleration. final Context context = view.getContext(); if (context != null && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ..... //第二件事情 创建ViewRootImpl,将View添加到列表中,这里每次都会new一个对象,所以说调用一次addView,就会有一个ViewRootImpl。 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { //第三件事情 :使用ViewRootImpl对象,调用setView root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
ViewRootImpl是是个什么呢?
- 简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责:1. 负责为应用程序窗口视图创建Surface。 2. 配合WindowManagerService来管理系统的应用程序窗口。 3. 负责管理、布局和渲染应用程序窗口视图的UI。*我们在看ViewRootImpl的setView 方法。
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; ...... mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ //1、调用requestLayout方法进行绘制 requestLayout(); .... try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); //2、调用mWindowSession添加View res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } ...... } }
看看requestLayout方法,首先检验是不是主线程在修改UI,然后 调用scheduleTraversals,在scheduleTraversals开始执行了,才会有我们熟悉的onDraw,onLayout,onMeasure。
@Override
public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals();http://www.cnblogs.com/GMCisMarkdownCraftsman/p/6117129.html } } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
二、APP端与WMS的IPC过程
继续回到setView方法,重点理解这行代码的意思。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
这行代码大部分人都知道这个是与WMS做IPC通信的,但是具体是个什么过程呢?我们可以将这个过程和AMS与应用进程IPC的过程做对比。
![](http://upload-images.jianshu.io/upload_images/1836169-c66747f4f190d33c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700)
ActivityManager类内部调用ActivityManagerNative的getDefault函数得到一个ActivityManagerProxy对象,AMS继承ActivityManagerNative,通过它可与AMS通信。所以说,ActivityManagerProxy(是AMS在客户端进程的一个代理,通过AMP里面的方法请求AMS。那么客户端进程在AMS的代理呢?这个代理就是ApplicationThreadProxy,如果AMS要通知Activity,那么就是使用ApplicationThreadProxy。现在看APP端和WMS的IPC。
![](http://upload-images.jianshu.io/upload_images/1836169-027dd9a115ddc93d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700)
App与WMS的连接,首先会建立一个Session到WMS,之后就会通过IWindowSession接口与WMS中的Session直接通信,IWindowSession类是什么?它指向了一个实现了IWindowSession接口的Session代理对象。当应用程序进程启动第一个Activity组件的时候,它就会请求WMS服务发送一个建立连接的Binder进程间通信请求。WMS服务接收到这个请求之后,就会在内部创建一个类型为Session的Binder本地对象,并且将这个Binder本地对象返回给应用程序进程,App就会得到一个Session代理对象,并且保存在ViewRootImpl类的成员变量mWindowSession中。大概代码如下。
public ViewRootImpl(Context context, Display display) { .... //创建了WindowSession对象 mWindowSession = WindowManagerGlobal.getWindowSession(); //也创建了W的对象mWindow mWindow = new W(this) .... }
private static IWindowSession sWindowSession;
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); //调用WMS的openSession创建一个Session对象 sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } }
com.android.server.wm.WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); Session session = new Session(this, callback, client, inputContext); return session; }
当App有了这个远端Session代理对象mWindowSession之后,所有向WMS的请求都通过mWindowSession来进行。举例来说:ViewRootImpl要添加窗口,就使用mWindowSession代理对象的addToDisplay方法调用到远端Session对象的addToDisplay方法。
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);
远端Session对象收到这个请求后,转接给WMS。
final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final WindowManagerService mService; ..... @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); } }
注意到addToDisplay中的参数mWindow是一个W对象,W对象是什么?ViewRootImpl::W:用于向WMS提供接口,让WMS控制App端的窗口。它可看作是个代理,很多时候会调用ViewRootImpl中的功能。
W的声明中有两个成员变量:mViewAncestor和mWindowSession,它一头连着App端的ViewRootImpl,一头连着WMS中的Session,且实现了IWindow的接口。意味着它是App和WMS的桥梁,是WMS用来回调App端,让ViewRootImpl做事用的。
举例来说,dispatchAppVisibility()的流程就是经过它来完成的:WMS ->ViewRootImpl::W->ViewRootHandler->handleAppVisibility()->scheduleTraversals()。
类似这种话结构的还有,PhoneWindow::DecorView,这种设计使得系统满足最小隔离原则,Client端该用到哪些接口就暴露哪些接口。因为这种类的函数都是跑在Binder线程中的,所以其中不能调用非线程安全的函数,也不能直接操作UI控件,所以一般都是往主线程消息队列里丢一个消息让其异步执行。
三、addWindow方法解读
addWindow方法比较长,粗略的分成下面9点来解释。
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { 1、窗口添加权限校验 2、检查特殊窗口attr.token和attr.type的一致性 3、创建窗口对象 4、调用adjustWindowParamsLw对窗口参数进行调整 5、创建pipe,用于输入消息的传递 6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中 7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow 8、添加窗口到Windows列表,确定窗口的位置 9、窗口已经添加了,调用assignLayersLocked调整一下层值 }
1、窗口添加权限校验
int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; }
从参数attrs中可以取出窗口的type,对type类型除了Toast窗口、屏保窗口、输入法窗口、墙纸窗口、语音交互窗口等少数几个类型窗口不需要进行权限判断外,其余的窗口都需要检查是否有android.Manifest.permission.SYSTEM_ALERT_WINDOW权限或者 android.Manifest.permission.INTERNAL_SYSTEM_WINDOW权限。
2、检查特殊窗口attr.token和attr.type的一致性
synchronized(mWindowMap) {
//屏幕没有准备好,不给添加窗口
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized"); } //获取当前窗口需要添加在哪一个屏幕上 final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent == null) { Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } //该窗口有没有权限在这个屏幕上添加 if (!displayContent.hasAccess(session.mUid)) { Slog.w(TAG_WM, "Attempted to add window to a display for which the application " + "does not have access: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } //该窗口是否已经添加过,WMS会把添加的窗口Token保存在mWindowMap中 if (mWindowMap.containsKey(client.asBinder())) { Slog.w(TAG_WM, "Window " + client + " is already added"); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } //如果窗口类型是子窗口 if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { //把这个子窗口的父窗口取出来 attachedWindow = windowForClientLocked(null, attrs.token, false); //父窗口如果为空,就会return,因为子窗口需要依赖一个父窗口存在 if (attachedWindow == null) { Slog.w(TAG_WM, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } //父窗口不为null,但是父窗口的类型也是一个子窗口,也会return if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) { Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } //这个type表示该窗口是要添加到虚拟设备上的,但是该设备却不是虚拟的,也要返回 if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display. Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; } boolean addToken = false; //从mTokenMap取出WindowToken,WindowToken是窗口分组的标志,因为addWindow方法调用之前,AMS等服务会提前向WMS注册一个token到mTokenMap,所以这里一般是可以取到的,也有些情况是取不到的,下面分析。 WindowToken token = mTokenMap.get(attrs.token); AppWindowToken atoken = null; boolean addToastWindowRequiresToken = false; if (token == null) { //如果这个窗口是应用程序窗口,上面取出的token为null,需要返回 if (type >= FIRST_APPLICATION_WINDOW && type <= 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,需要返回 if (type == TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果这个窗口是语音交互窗口,上面取出的token为null,需要返回 if (type == TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果这个窗口是墙纸窗口,上面取出的token为null,需要返回 if (type == TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果这个窗口是屏保窗口,上面取出的token为null,需要返回 if (type == TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果这个窗口QS_DIALOG,上面取出的token为null,需要返回 if (type == TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } //如果这个窗口TYPE_ACCESSIBILITY_OVERLAY类型窗口,上面取出的token为null,需要返回 if (type == TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_TOAST) { // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, attachedWindow)) { Slog.w(TAG_WM, "Attempted to add a toast window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } //如果不是上面这些类型窗口,并且从mWindowMap中又不能取出对应的token的话, 这里会隐士的创建一个第四个参数等于false,代表隐士创建,隐士创建的token在窗口销毁的时候,是不需要移除的 token = new WindowToken(this, attrs.token, -1, false); addToken = true; //上面取出的窗口不为null,并且type是应用类型的窗口 } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { //取出appWindowToken,appWindowToken是WindowToken的子类,一个appWindowToken一般表示这个窗口是一个Activity窗口 atoken = token.appWindowToken; //取出atoken为null的话,不能添加,窗口的添加是必须有一个token的 if (atoken == null) { Slog.w(TAG_WM, "Attempted to add window with non-application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { Slog.w(TAG_WM, "Attempted to add window with exiting application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_APP_EXITING; } //如果这个窗口的类型是一个启动窗口的话,atoken的firstWindowDrawn等于true,不需要添加 if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v( TAG_WM, "**** NO NEED TO START: " + attrs.getTitle()); return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } } else if (type == TYPE_INPUT_METHOD) { //窗口的类型是输入法窗口,但是它的token不是输入法类型的token,也要返回,不给添加,下面几个是类似的 if (token.windowType != TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_VOICE_INTERACTION) { if (token.windowType != TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_WALLPAPER) { if (token.windowType != TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_DREAM) { if (token.windowType != TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_ACCESSIBILITY_OVERLAY) { if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_TOAST) { // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName, callingUid, attachedWindow); if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) { Slog.w(TAG_WM, "Attempted to add a toast window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_QS_DIALOG) { if (token.windowType != TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (token.appWindowToken != null) { Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type); // It is not valid to use an app token with other system types; we will // instead make a new token for it (as if null had been passed in for the token). attrs.token = null; token = new WindowToken(this, null, -1, false); addToken = true; } ....... }
3、创建窗口对象
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
WindowState是WMS中真正的窗口对象
4、调用PhoneWindowManager的adjustWindowParamsLw调整布局参数
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_SYSTEM_OVERLAY: case TYPE_SECURE_SYSTEM_OVERLAY: attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; break; case TYPE_STATUS_BAR: if (mKeyguardHidden) { attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } break; case TYPE_SCREENSHOT: attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; break; case TYPE_TOAST: if (attrs.hideTimeoutMilliseconds < 0 || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) { attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT; } attrs.windowAnimations = com.android.internal.R.style.Animation_Toast; break; } if (attrs.type != TYPE_STATUS_BAR) { // The status bar is the only window allowed to exhibit keyguard behavior. attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } if (ActivityManager.isHighEndGfx()) { if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } final boolean forceWindowDrawsStatusBarBackground = (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0; if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceWindowDrawsStatusBarBackground && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) { attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; } } }
这段代码主要调整状态栏等几个特殊窗口的FLAG,比如不让它获取用户焦点,不让它响应触摸消息等。
5、创建pipe,用于输入消息的传递
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0); if (openInputChannels) { win.openInputChannel(outInputChannel); }
窗口需要接收事件,所以需要向InputManagerService注册InputChannel。
6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中
if (addToken) {
mTokenMap.put(attrs.token, token);
}
win.attach();
mWindowMap.put(client.asBinder(), win);
void attach() { if (WindowManagerService.localLOGV) Slog.v( TAG, "Attaching " + this + " token=" + mToken + ", list=" + mToken.windows); mSession.windowAddedLocked(); }
void windowAddedLocked() { if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( TAG_WM, "First window added to " + this + ", creating SurfaceSession"); //创建窗口所对应的SurfaceSession mSurfaceSession = new SurfaceSession(); if (SHOW_TRANSACTIONS) Slog.i( TAG_WM, " NEW SURFACE SESSION " + mSurfaceSession); mService.mSessions.add(this); if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) { mService.dispatchNewAnimatorScaleLocked(this); } } mNumWindow++; }
7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow
if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
token.appWindowToken.startingWindow = win;
if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + token.appWindowToken
+ " startingWindow=" + win); }
8、添加窗口到Windows列表,确定窗口的位置
if (type == TYPE_INPUT_METHOD) {
win.mGivenInsetsPending = true;
mInputMethodWindow = win;
addInputMethodWindowToListLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) { mInputMethodDialogs.add(win); addWindowToListInOrderLocked(win, true); moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); imMayMove = false; } else { addWindowToListInOrderLocked(win, true); if (type == TYPE_WALLPAPER) { mWallpaperControllerLocked.clearLastWallpaperTimeoutTime(); displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) { // If there is currently a wallpaper being shown, and // the base layer of the new window is below the current // layer of the target window, then adjust the wallpaper. // This is to avoid a new window being placed between the // wallpaper and its target. displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } }
确定窗口位置,分成几种情况,如果是输入法类型的窗口,调用addInputMethodWindowToListLocked插入窗口列表,如果是输入法对话框,调用addWindowToListInOrderLocked插入窗口列表,如果是其他类型的窗口,比如应用类型的窗口,调用addWindowToListInOrderLocked插入窗口列表,现在重点关注addWindowToListInOrderLocked方法。
private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) { if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win + " Callers=" + Debug.getCallers(4)); if (win.mAttachedWindow == null) { final WindowToken token = win.mToken; //tokenWindowsPos表示该WindowState对象在所属同一WindowToken的所有WindowState中的位置 int tokenWindowsPos = 0; if (token.appWindowToken != null) { tokenWindowsPos = addAppWindowToListLocked(win); } else { addFreeWindowToListLocked(win); } if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token); // token.windows就是描述所属该token下的所有WindowState对象 // 比如一个activity弹出了一个AlertDialog窗口,这两个窗口的AppWindowToken是一个 token.windows.add(tokenWindowsPos, win); } } else { addAttachedWindowToListLocked(win, addToToken); } final AppWindowToken appToken = win.mAppToken; if (appToken != null) { if (addToToken) { appToken.addWindow(win); } } }
先看窗口的父窗口mAttachedWindow是不是存在,如果不存在,说明这个窗口应用类型的窗口,反之就是一个子窗口,走else分之,使用addAttachedWindowToListLocked方法将子窗口插入窗口列表。假设不是一个子窗口,那么需要取出这个窗口的token,如果token里面的appWindowToken不为null的话,就表明这是一个Activity窗口,需要调用addAppWindowToListLocked将Activity窗口插入窗口堆栈,反之使用addFreeWindowToListLocked方法将非Activity窗口插入窗口堆栈。
private int addAppWindowToListLocked(final WindowState win) { //获取WindowState要插入的屏幕对象displayContent final DisplayContent displayContent = win.getDisplayContent(); if (displayContent == null) { // It doesn't matter this display is going away. return 0; } final IWindow client = win.mClient; final WindowToken token = win.mToken; //获取当前屏幕上所有窗口列表对象windows final WindowList windows = displayContent.getWindowList(); //获取属于displayContent这个屏幕上,属于token所描述的WindowList,WindowList存放了token所描述的WindowState的列表 WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); int tokenWindowsPos = 0; //tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入 //比如当一个Activity窗口上弹出一个Dialog窗口, //那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走这个逻辑 if (!tokenWindowList.isEmpty()) { return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList); } // No windows from this token on this display if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window " + client.asBinder() + " (token=" + token + ")"); // Figure out where the window should go, based on the // order of applications. //pos记录源码插入的WindowState WindowState pos = null; //遍历这个屏幕中的Task final ArrayList<Task> tasks = displayContent.getTasks(); int taskNdx; int tokenNdx = -1; //自顶向下遍历Task,每一个Task中取出它的tokens列表 for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; //自顶向下遍历tokens列表,就可以取出AppWindowToken for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { final AppWindowToken t = tokens.get(tokenNdx); //一般来说,第一个就是要匹配的AppWindowToken,因为都是自顶向下遍历的,这个时候,跳出循环 if (t == token) { //tokenNdx记录着AppWindowToken在tokens中的位置 --tokenNdx; //如果tokenNdx小于0,那么是无效的,需要取下一个Task,把它插入下一个Task的顶部 if (tokenNdx < 0) { --taskNdx; if (taskNdx >= 0) { tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1; } } break; } // We haven't reached the token yet; if this token // is not going to the bottom and has windows on this display, we can // use it as an anchor for when we do reach the token. //如果当前tasks中第一个AppWindowToken不等于t,那么执行getTokenWindowsOnDisplay, //获取属于displayContent这个屏幕上,属于token所描述的WindowList tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); if (!t.sendingToBottom && tokenWindowList.size() > 0) { //pos就指向当前遍历到AppWindowToken所属的最下面一个的WindowState pos = tokenWindowList.get(0); } } if (tokenNdx >= 0) { // early exit break; } } // We now know the index into the apps. If we found // an app window above, that gives us the position; else // we need to look some more. //如果pos不为null,说明pos是上面指向的当前遍历到AppWindowToken所属的最下面一个的WindowState if (pos != null) { // Move behind any windows attached to this one. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { tokenWindowList = getTokenWindowsOnDisplay(atoken, displayContent); final int NC = tokenWindowList.size(); if (NC > 0) { WindowState bottom = tokenWindowList.get(0); if (bottom.mSubLayer < 0) { //判断pos的token所描述的窗口列表中最下面的窗口对象bottom的子序是否小于0, //如果小于0,pos需要指向这个窗口, 确保pos指向的是上面AppWindowToken的最后一个WindowState pos = bottom; } } } //插入最下面的一个WindowState的后面 placeWindowBefore(pos, win); return tokenWindowsPos; } // Continue looking down until we find the first // token that has windows on this display. //走到这里,说明第一个就是要匹配的AppWindowToken,taskNdx记录了是在哪一个taskNdx中,,再次自定向下遍历 for (; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; for (; tokenNdx >= 0; --tokenNdx) { //获取顶部最顶部Task中最顶部的AppWindowToken final AppWindowToken t = tokens.get(tokenNdx); tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); final int NW = tokenWindowList.size(); if (NW > 0) { //pos尽可能的指向最顶部TASK中最顶部的AppWindowToken所描述的WindowState pos = tokenWindowList.get(NW - 1); break; } } if (tokenNdx >= 0) { // found break; } } //pos!= null,说明找到了,检查pos描述的窗口上面有没有子窗口,如果有,需要把pos指向最顶部的子窗口 if (pos != null) { // Move in front of any windows attached to this // one. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { WindowState top = atoken.windows.get(NC - 1); if (top.mSubLayer >= 0) { pos = top; } } } //把win插入到pos的后面 placeWindowAfter(pos, win); return tokenWindowsPos; } // Just search for the start of this layer. // 走到这里,说明该WindowState是新启动的一个activity的第一个窗口(新task的第一个WindowState), // 因为新启动的窗口是没有可参考的activity窗口,所以需要通过mBaseLayer去插入 final int myLayer = win.mBaseLayer; int i; for (i = windows.size() - 1; i >= 0; --i) { WindowState w = windows.get(i); // Dock divider shares the base layer with application windows, but we want to always // keep it above the application windows. The sharing of the base layer is intended // for window animations, which need to be above the dock divider for the duration // of the animation. if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) { break; } } if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + windows.size()); windows.add(i + 1, win); mWindowsChanged = true; return tokenWindowsPos; }
上面有检查tokenWindowList,tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入,比如当一个Activity窗口上弹出一个Dialog窗口,那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走addAppWindowToTokenListLocked这个逻辑。
private int addAppWindowToTokenListLocked(WindowState win, WindowToken token, WindowList windows, WindowList tokenWindowList) { int tokenWindowsPos; // If this application has existing windows, we // simply place the new window on top of them... but // keep the starting window on top. //如果是TYPE_BASE_APPLICATION窗口,则需要插入在该AppWindowToken所有窗口的最底部 if (win.mAttrs.type == TYPE_BASE_APPLICATION) { // Base windows go behind everything else. WindowState lowestWindow = tokenWindowList.get(0); placeWindowBefore(lowestWindow, win); tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows); } else { AppWindowToken atoken = win.mAppToken; final int windowListPos = tokenWindowList.size(); WindowState lastWindow = tokenWindowList.get(windowListPos - 1); // 如果是starting window,则插入到starting window的下面 if (atoken != null && lastWindow == atoken.startingWindow) { placeWindowBefore(lastWindow, win); tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows); } else { int newIdx = findIdxBasedOnAppTokens(win); //there is a window above this one associated with the same //apptoken note that the window could be a floating window //that was created later or a window at the top of the list of //windows associated with this token. if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " + windows.size()); windows.add(newIdx + 1, win); if (newIdx < 0) { // No window from token found on win's display. tokenWindowsPos = 0; } else { tokenWindowsPos = indexOfWinInWindowList( windows.get(newIdx), token.windows) + 1; } mWindowsChanged = true; } } return tokenWindowsPos; }
窗口的插入还是比较复杂的,总结而言:
1.非应用窗口依据mBaseLayer插入,越高越靠前,墙纸和输入法会有特殊处理,在下面还会进行调整。
2.应用窗口参考activity的位置插入,通常应该被插入在其activity所在task的顶部或者该activity上面的activity的最后一个窗口的下面
3.子窗口依据mSubLayer插入
最终插入的地方有两个:DisplayContent所持有的记录该屏幕下所有窗口顺序的WindowList,以及新窗口的WindowToken所记录的所有属于它的WindowState中的WindowList列表。
9、窗口已经添加了,调用assignLayersLocked调整一下层值
//参数windows是窗口列表
final void assignLayersLocked(WindowList windows) { if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows, new RuntimeException("here").fillInStackTrace()); clear(); int curBaseLayer = 0; int curLayer = 0; boolean anyLayerChanged = false; //遍历窗口列表,上面通过Z序的计算公式计算出来的Z序值保存在WindowState的变量mBaseLayer 中,这个循环的意思是,遇到同类型的窗口,后一个窗口在前一个窗口的基础上偏移5。 for (int i = 0, windowCount = windows.size(); i < windowCount; i++) { final WindowState w = windows.get(i); boolean layerChanged = false; int oldLayer = w.mLayer; if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { curLayer += WINDOW_LAYER_MULTIPLIER; } else { curBaseLayer = curLayer = w.mBaseLayer; } // 更新该窗口的mAnimLayer,也就是动画显示时,该窗口的层级 assignAnimLayer(w, curLayer); // TODO: Preserved old behavior of code here but not sure comparing // oldLayer to mAnimLayer and mLayer makes sense...though the // worst case would be unintentionalp layer reassignment. if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) { layerChanged = true; anyLayerChanged = true; } // 将当前应用窗口的最高显示层级记录在mHighestApplicationLayer中 if (w.mAppToken != null) { mHighestApplicationLayer = Math.max(mHighestApplicationLayer, w.mWinAnimator.mAnimLayer); } // 对于分屏等相关的窗口,它们的显示层级需要再次处理 collectSpecialWindows(w); if (layerChanged) { w.scheduleAnimationIfDimming(); } } // 调整特殊窗口的层级 adjustSpecialWindows(); //TODO (multidisplay): Magnification is supported only for the default display. if (mService.mAccessibilityController != null && anyLayerChanged && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { mService.mAccessibilityController.onWindowLayersChangedLocked(); } if (DEBUG_LAYERS) logDebugLayers(windows); }
addWindow流程大致上分成以上九点,基本可以了解一个窗口是怎么添加到WMS中的。
作者:LooperJing
链接:https://www.jianshu.com/p/ba53cf8694f1
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。