[Android] Window的添加过程

一、Window世家

  • frameworks/base/core/java/android/view/ViewManager.java

    属于一个接口类,实现了对view的更新,添加,移除,具体代码如下

    public interface ViewManager
    {
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    

    可以看到接收的参数都是view,说明Window管理的都是view,WindowManager继承了该接口,注意是继承并不是实现,后续操作window都靠实现WM

  • frameworks/base/core/java/android/view/WindowManager.java

    实现了ViewManager接口,因为WM本身就是一个接口类,所以并没有重写VM接口里面的方法,后续会通过getSystemService拿到WM,强制至WindowManagerImpl,因为WMI实现了WM接口,然后在WMI中实现了addView的具体逻辑,可以看看WMI被创建的地方如下

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);//1
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);//2
    }
    
  • frameworks/base/core/java/android/view/Window.java

    Window是一个抽象类,它具体的实现类为PhoneWindow,Window中管理了view,例如我们常知的setContentView,findViewById,setTitle,setBackgroundDrawable

    一个activity对应了一个Window(也可以称之为PhoneWindow)

  • frameworks/base/core/java/android/app/Activity.java

    一个Activity包含了一个Window(PhoneWindow),也包含了一个WMI,主要的方法是attach方法,就是window创建的开始处,也是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) {
            attachBaseContext(context);
            mFragments.attachHost(null /*parent*/);
            mWindow = new PhoneWindow(this, window);//1
            ...
            mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
          ...
    
  • frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    PhoneWindow在Activity#attach中创建,一个activity对应一个PhoneWindow,它继承自抽象类Window,包含了DecorView,也重写了Window的setContentView方法,当内部的mContentParent(类型是ViewGroup,layout文件中的根view,例如xml中是线性布局开头)为空的时候,就会开始创建DecorView,然后开始解析activity,分为两部分,第一部分是title,第二部分是content,作为DecorView的内容,然后DecorView的content部分就是mContentParent,目前还是个空壳,没有实际内容,还没有解析布局文件

  • frameworks/base/core/java/android/view/WindowManagerGlobal.java

    属于一个单例模式,一个进程存在一个此对象,在WMI中的addView中会被调用,WMG中也有个addView,属于WMI中调用过来的,这里面的逻辑才是真正的处理view添加的逻辑,例如处理WindowManager.LayoutParamsnew ViewRootImpl(),调用ViewRootImpl.setView,接着会调用IWindowSession向WMS中的Session发起窗口添加流程

    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);//1
          } else {
          ...
          }
    
          ViewRootImpl root;
          View panelParentView = null;
           ...
              root = new ViewRootImpl(view.getContext(), display);//2
              view.setLayoutParams(wparams);
              mViews.add(view);
              mRoots.add(root);//3
              mParams.add(wparams);
          }
          try {
              root.setView(view, wparams, panelParentView);//4
          } catch (RuntimeException e) {
             ...
          }
      }
    
  • frameworks/base/core/java/android/view/WindowManagerImpl

    在activity#attach中,调用了mWindow.setWindowManager中被创建,是通过getSystemService获取了WM,然后强转为WMI,调用WindowManagerImpl的createLocalWindowManager(this),来创建WMI实例,这个this就是activity的PhoneWindow(说Window也行),然后这个this就作为父window(主window)

    WMI实现了WM接口,所以WMI调用了addView,拥有具体实现,会继续调用WindowManagerGlobal进行处理,WMI拥有phonewindow的引用,后续将调用addView对PhoneWindow作为参数进行传递,然后将view绑定到这个PhoneWindow上

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    
  • frameworks/base/service/core/java/com/android/server/wm/WindowState.java

二、Window添加流程

先大概过一下总体逻辑(因为添加view都是通过WM.addView,所以我们目的是分析addView,晚点再分析setContentView):

  1. Activity#attach中创建了PhoneWindow(继承了Window抽象类),拥有setContentView等属性
  2. Activity#attach中又调用了PhoneWindow.setWindowManager(实际上是Window里面的方法),来创建了WindowManagerImpl并和mWindow(PhoneWindow)进行绑定,mWindow在WMI中属于parentWindow
  3. WindowManagerImpl中又实现了WindowManager,有addView的具体实现,又继续调用的WindowManagerGlobal#addView
  4. 在WindowManagerGlobal#addView中处理了LayoutParams(只是保存到mParams,然后给view添加此params,并没有根据参数改变view行为的逻辑,没有在这里处理,设计这种行为的属于view绘制流程),然后创建了viewRootImpl
  5. 接着调用了viewRootImpl#setView(view,params,parentView)方法,view就是要添加的view,parentView就是PhoneWindow(Window),viewRootImpl的一些职责 :管理view,负责和WMS进行通信,负责view的绘制,测量,布局
  6. 在setView中只处理mView为空的情况,因为必须是根view,并重新赋值给mView(它是一个IBinder对象,因为需要将view跨进程传递到WMS中,属于W类,属于IWindow.aidl),其次调用了mWindowSession.addToDisplayAsUser方法,mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前包含ViewRootImpl在内的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WMS所在的进程,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session
  7. 所以我们直接看Session.java#中的addToDisplayAsUser方法,是从viewRootImpl—>mWindowSession.addToDisplayAsUser跨进程过来的,session又继续调用了WMS的addWindow方法,所以继续锁定WMS
  8. WMS#addWindow中维护了WindowState,创建了DisplayContent,然后为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上

三、源码分析

主要分析WMS的public int addWindow()方法

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility...) {
       ...
        // 父类window
        WindowState parentWindow = null;
       ...
        final int type = attrs.type;

        synchronized (mGlobalLock) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }
						// 获取屏幕
            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;
            }
            if (!displayContent.hasAccess(session.mUid)) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add window to a display for which the application "
                                + "does not have access: %d.  Aborting.",
                        displayContent.getDisplayId());
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
						// 检查Window是否以及存在(也就是WindowState),如果存在于集合中则代表重复添加
            if (mWindowMap.containsKey(client.asBinder())) {
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            // 1000~1999属于sub窗口类型,说明这个窗口是一个子窗口,所以需要找父类窗口
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                // 通过ViewRootImpl.W中的代理对象且从mWindowMap取出WindowState
                // LayoutParams中保存了WindowState对应的IBinder代理
                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 (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add private presentation window to a non-private display.  "
                                + "Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add presentation window to a non-suitable display.  "
                                + "Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
						...
						// 检查该window是否存在父类窗口,是否具有ActivityRecord
            ActivityRecord activity = null;
            final boolean hasParent = parentWindow != null;
						// 获取token,有可能为空
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // 如果有父类,则获取父类的窗口type
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;
						**// 1~99属于应用程序窗口类型**
						if (rootType >= FIRST_APPLICATION_WINDOW
                    && rootType <= LAST_APPLICATION_WINDOW) {
                // 判断当前窗口是否重复添加
               ...
                **// 输入法窗口,也就是:FIRST_SYSTEM_WINDOW(2000)+11**
            } else if (rootType == TYPE_INPUT_METHOD) {
                // 检查窗口是否可以被添加
                if (token.windowType != TYPE_INPUT_METHOD) {
                    ProtoLog.w(WM_ERROR, "Attempted to add input method window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } 
             **// 壁纸窗口类型,FIRST_SYSTEM_WINDOW(2000)+13**
            if (rootType == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            }  else if (type == TYPE_TOAST) { **// toast窗口类型:FIRST_SYSTEM_WINDOW(2000)+5**
                // 检查窗口的有效性,是否可以被添加
                ...
                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                    ProtoLog.w(WM_ERROR, "Attempted to add a toast window with bad token "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } 
						...
            // 创建WindowState对象,client(IBinder/IWindow)都可以代表WindowState对象
            // 和父类window(也是WindowState),client,session,WMS,attrs进行绑定
            // 它存有窗口的所有的状态信息,在WMS中它代表一个窗口,client就是代表了这个WindowState
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            ...
            // 如果WindowState->WindowToken中找不到屏幕,则直接返回无效屏幕的错误
            if (win.getDisplayContent() == null) {
                ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
            // 调整Toast窗口超时时间等
            displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
           ...

            ...
            // 处理Toast超时后隐藏窗口
            if (type == TYPE_TOAST) {
                if (!displayContent.canAddToastWindowForUid(callingUid)) {
                    ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time.");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
               ...
                if (addToastWindowRequiresToken
                        || (attrs.flags & FLAG_NOT_FOCUSABLE) == 0
                        || displayContent.mCurrentFocus == null
                        || displayContent.mCurrentFocus.mOwnerUid != callingUid) {
                    mH.sendMessageDelayed(
                            mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                            win.mAttrs.hideTimeoutMilliseconds);
                }
            }

           ...

            // WindowState 的绑定方法,实际上就是调用了 mSession.windowAddedLocked();
            // 具体作用:创建SessionSurface,然后往WMS.mSessions集合中,把当前session添加到集合中
            // 说明一个window对应了一个session,然后mNumWindow++,记录window的个数
            win.attach();
            // 将WindowState代理对象,保存到mWindowMap中
            mWindowMap.put(client.asBinder(), win);
           ...

            // 将当前WindowState添加到WindowToken中
            win.mToken.addWindow(win);
            // 针对当前window是否属于导航栏窗口还是状态栏窗口,进行处理
            displayPolicy.addWindowLw(win, attrs);
            ...

        return res;
    }

总结一下以上内容:

  • 先获取当前window是否具有父类window,若有则复用父类window的WindowToken,若没有则创建WindowToken
  • 然后开始检查屏幕有效性,是否存在屏幕等,以及窗口的有效性,例如父类window是子窗口类型,子window也是子窗口类型,那么就无法添加
  • 然后针对窗口类型:子窗口(10001999),输入法窗口/壁纸窗口/Toast窗口等属于系统窗口(2000~2041,每次都是2000+1进行累加,都代表不同的系统窗口,例如`FIRST_SYSTEM_WINDOW(2000)+11`就代表着输入法窗口),还有应用程序窗口(199)
  • 针对以上的窗口类型,检查窗口的有效性,是否重复添加,以及Toast窗口的超时机制
  • 接着创建WindowState对象,client(IBinder/IWindow)都可以代表WindowState对象,然后和父类window(也是WindowState),client,session,WMS,attrs进行绑定,它存有窗口的所有的状态信息,在WMS中它代表一个窗口,client就是代表了这个WindowState
  • 然后将WindowState对象保存到了mWindowMap中,Key就是它的IBinder代理,value就是WindowState,可以通过这个Map集合检查窗口是否存在,是否重复添加
  • 然后调用了WindowState的attach方法,主要作用:实际上就是调用了 mSession.windowAddedLocked();创建SessionSurface,然后往WMS.mSessions集合中,把当前session添加到集合中,说明一个window对应了一个session,然后mNumWindow++,记录window的个数
  • 将当前WindowState添加到WindowToken中

继续分析一下WindowState的构造方法

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
            PowerManagerWrapper powerManagerWrapper) {
        super(service);
        mTmpTransaction = service.mTransactionFactory.get();
        mSession = s;
        mClient = c;
        mAppOp = appOp;
        mToken = token;
        mActivityRecord = mToken.asActivityRecord();
        mOwnerUid = ownerId;
        mShowUserId = showUserId;
        mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
        mWindowId = new WindowId(this);
        mAttrs.copyFrom(a);
        mLastSurfaceInsets.set(mAttrs.surfaceInsets);
        mViewVisibility = viewVisibility;
        mPolicy = mWmService.mPolicy;
        mContext = mWmService.mContext;
        DeathRecipient deathRecipient = new DeathRecipient();
        mPowerManagerWrapper = powerManagerWrapper;
        mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
        mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
                mActivityRecord != null
                        ? mActivityRecord.getInputApplicationHandle(false /* update */) : null,
                getDisplayId()));
        ...

        // 如果当前window的类型为子窗口
        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
            // The multiplier here is to reserve space for multiple
            // windows in the same type layer.
            // 获取基础layer
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            // 子窗口会依赖基础layer
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
            mIsChildWindow = true;

            mLayoutAttached = mAttrs.type !=
                    WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
            // 父类是否是输入法窗口类型
            mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
                    || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
            // 父类是否是壁纸窗口类型
            mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
        }
       ...
				// 窗口动画
        mWinAnimator = new WindowStateAnimator(this);
        mWinAnimator.mAlpha = a.alpha;

        mRequestedWidth = 0;
        mRequestedHeight = 0;
        mLastRequestedWidth = 0;
        mLastRequestedHeight = 0;
        mLayer = 0;
        mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
                mAttrs.packageName, s.mUid);
        // 如果是子窗口类型,将当前WindowState添加到父类窗口中
        if (mIsChildWindow) {
            ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
            parentWindow.addChild(this, sWindowSubLayerComparator);
        }
    }

以上内容就是将session,WMS,client,WindowToken,LayoutParams,以及父类WindowState进行绑定关联,client(IBinder/IWindow)都可以代表WindowState对象,然后判断当前窗口是否是子窗口,是否具有父类窗口,然后将当前窗口添加到父类窗口中:

parentWindow.addChild(this, sWindowSubLayerComparator);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值