Android中WindowManager的理解

WindowManager简介

WindowManager(WM)是一个接口类,继承自接口ViewManager,它是用来管理Window的。它的实现类为WindowManagerImpl。对窗口的添加,更新和删除操作,由WM负责。它会将具体的工作交由WMS来处理,WM和WMS通过Binder来进行进程间通信,WMS作为系统服务有很多API是不会暴露给WM的

WindowManager的关联类

  • WindowManager继承自ViewManager,ViewManager中定义了3个方法,分别用来添加,更新和删除View

    public interface ViewManager{
          //添加view
        public void addView(View view, ViewGroup.LayoutParams params);
          //更新view
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
          //删除view
        public void removeView(View view);
    }
    

    WindowManager在继承了ViewManager的同时,又加入了很多功能,包括Window的类型和层级相关的常量,内部类以及一些方法,其中有2个方法是根据Window的特性加入的:

          //这个方法能够得知这个windowManager实例将window添加到哪个屏幕上了,也就是得到WindowManager所管理的屏幕
          public Display getDefaultDisplay();
          //规定在这个方法返回前要立即执行View.onDetachedFromWindow(),来完成传入的View的相关的销毁工作
          public void removeViewImmediate(View view);
    
  • Window是一个抽象类,它的具体实现类为PhoneWindow,PhoneWindow在Activity启动时最终会在它的attach()中被创建。

        final void attach() {
              //创建PhoneWindow
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
                  //设置windowManager
            mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
                //对持有的WindowManager引用赋值
            mWindowManager = mWindow.getWindowManager();
        }
    

    可以看到,这首先构造了PhoneWidow,然后设置了WindowManager,WindowManager通过getSystemService()方式获得,看setWindowManager()的代码:

           public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                   boolean hardwareAccelerated) {
               if (wm == null) {
                   wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
               }
               mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
           }
    

    如果传入的WM为null,那么就通过ContextImpl.getSystemService()再获取一次,那么看一下获取的方法:

           @Override
           public Object getSystemService(String name) {
               return SystemServiceRegistry.getSystemService(this, name);
           }
    

    接着调用了SystemServiceRegistry.getSystemService(),继续看:

           public static Object getSystemService(ContextImpl ctx, String name) {
               ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
               return fetcher != null ? fetcher.getService(ctx) : null;
           }
    

    这里从SYSTEM_SERVICE_FETCHERS取出一个ServiceFetcher,最终取出需要的Service,那么看一下这个SYSTEM_SERVICE_FETCHERS:

          //将Service注册
              private static  void registerService(String serviceName, Class serviceClass,
                   ServiceFetcher serviceFetcher) {
               SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
               SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
           }
               //在静态代码块中注册具体的Service
              registerService(Context.WINDOW_SERVICE, WindowManager.class,
                       new CachedServiceFetcher() {
                   @Override
                   public WindowManager createService(ContextImpl ctx) {
                       return new WindowManagerImpl(ctx);
                   }});
    

    SystemServiceRegistry的静态代码块中通过registerService()注册了各种Service,这里可以看到Context.WINDOW_SERVICE对应的是WindowManagerImpl,那么最终getSystemService()得到的就是WindowManagerImpl对象。

    继续看setWindowManager():

           public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                   boolean hardwareAccelerated) {
               if (wm == null) {
                   wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
               }
               mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
           }
    

    前面的分析已经知道wm实际是WindowManagerImpl,这里又调用了createLocalWindowManager(),那么看代码:

           public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
               return new WindowManagerImpl(mContext, parentWindow);
           }
    
    

    可以看到,这里同样也是创建了WindowManagerImpl,不过这次将window传了进去,这样WindowManagerImpl就持有了window的引用。

    WindowManagerImpl持有了window的引用以后,就可以对window进行操作,如addVIew(),看代码:

        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
              //mGlobal是WindowManagerGlobal类型
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    
    

    这里可以看到,addView()的功能又转交给了WindowManagerGlobal

    再看WindowManagerGlobal:

    public final class WindowManagerGlobal {
    
        private static IWindowManager sWindowManagerService;
          //负责与WMS进行通信
        private static IWindowSession sWindowSession;
    
          //mView记录了所有View树的根节点(每个布局最外层的view)
        private final ArrayList mViews = new ArrayList();
          //mRoots记录了所有Activity的ViewRootImpl
        private final ArrayList mRoots = new ArrayList();
        //mParams记录了所有window的属性
        private final ArrayList mParams =
                new ArrayList();
        private final ArraySet mDyingViews = new ArraySet();
    
          //单例对象
        public static WindowManagerGlobal getInstance() {
            synchronized (WindowManagerGlobal.class) {
                if (sDefaultWindowManager == null) {
                    sDefaultWindowManager = new WindowManagerGlobal();
                }
                return sDefaultWindowManager;
            }
        }
    
    

    可以看到WindowManagerGlobal中定义了sWindowSession,负责与WMS的通信,mViews记录了所有view树的根节点,mRoots记录了所有Activity的ViewRootlmpl,mParams记录了所有window的属性。

    这里的WindowManagerGlobal是一个单例对象,一个进程只有一个。而WindowManagerImpl从它的创建来看,是有多个实例的

    总结:PhoneWindow继承自WIndow,Window通过setWindowManager()与WindowManager产生关联。WindowManager继承自接口ViewManager,WindowManagerImpl是WIndowManager接口的实现类,但是具体功能会委托给WindowManagerGlobal来实现。

Window的属性

Window为了更好地与WMS通讯,它们之间制定了一些协议,这个协议就是Window的属性。这些属性定义在WindowManager的LayoutParams中。Window的属性有很多种,与开发关系密切的有3种,它们分别是Type(window的类型),Flag(Window的标志)和SoftInputMode(软键盘相关模式)

  • Window的类型和显示次序

    • Window的类型分为三大类型:Application Window(应用程序窗口),Sub Window(子窗口),System Window(系统窗口)。每个大类型中又包含了很多种类型,它们都定义在了WindowManager的静态内部类LayoutParams中

      • 应用程序窗口:应用程序窗口的Type值的范围为1-99
      • 子窗口:它不能独立存在,需要附着在其他窗口上才可以。如PopupWindow。它的Type值范围为1000-1999
      • 系统窗口:Toast,输入法窗口,系统音量条窗口,系统错误窗口都属于系统窗口。系统窗口的Type值范围为2000-2999
    • 窗口的显示次序:当一个进程向WMS申请一个窗口时,WMS会为窗口确定显示次序。为了方便窗口显示次序管理,手机屏幕可以虚拟地用X,Y,Z轴来表示,其中Z轴垂直与屏幕,从屏幕内向屏幕外,这样确定窗口显示次序也就是确定窗口在Z轴上的次序,这个次序称为Z_Oder。Type值是Z_oder排序的依据,Type值越大则Z-Oder排序越靠前,就越靠近用户。

  • Window的标志

    window的标志也就是flag,用于控制window的显示,同样被定义在windowmanager的内部类LayoutParams中。

    设置Window的flag有3种方法:

    //第一种,内部也是调用setFlags()
    mWindow.addFlags()
    //第二种
    mWindow.setFlags()
    //第三种
    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
    layoutParams.flags=WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
    TextView textView = new TextView(this);
    windowManager.addView(textView,layoutParams);
    
    

Window的操作

对Window的操作,最终都会交由WMS来进行处理。窗口的处理分为2部分,一部分是WindowManager处理部分,一部分是WMS处理部分。对于不同的窗口类型(应用程序窗口,子窗口,系统窗口)添加过程有所不同,但是对于WMS处理的过程,基本上是一样的。WMS对这三大窗口基本是一视同仁的。

  • 系统窗口的添加过程

    这里看一下StatusBar的添加过程

           private void addStatusBarWindow() {
               makeStatusBarView();
               mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
               mRemoteInputController = new RemoteInputController(mHeadsUpManager);
             
               mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
           }
    
    

    继续再看这个add():

           public void add(View statusBarView, int barHeight) {
                       //通过WindowManager.LayoutParams配置属性
               mLp = new WindowManager.LayoutParams(
                       ViewGroup.LayoutParams.MATCH_PARENT,
                       barHeight,
                       WindowManager.LayoutParams.TYPE_STATUS_BAR,
                       WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                               | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                               | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                               | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                               | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                       PixelFormat.TRANSLUCENT);
               //调用windowManager的addView()
               mWindowManager.addView(mStatusBarView, mLp);
               mLpChanged = new WindowManager.LayoutParams();
           }
    
    

    这里调用了mWindowManager.addView()添加status,前面说过,具体是由WindowMangerImpl实现的,那么看一下它的addView():

        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    
    

    这里WindowManagerGlobal.addView(),看代码:

           public void addView(View view, ViewGroup.LayoutParams params,
                   Display display, Window parentWindow) {
    
               ViewRootImpl root;
               View panelParentView = null;
               //创建ViewRootImpl
               root = new ViewRootImpl(view.getContext(), display);
       
               view.setLayoutParams(wparams);
                       //添加view
               mViews.add(view);
               //添加root
               mRoots.add(root);
               //添加窗口参数
               mParams.add(wparams);
               //将窗口及窗口参数设置进ViewRootImpl中
               root.setView(view, wparams, panelParentView);
    
           }
    
    

    这里可以看到添加窗口的操作是通过ViewRootImpl进行的,看一下它的setView():

           public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                  //进行进程间通信
                          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                      getHostVisibility(), mDisplay.getDisplayId(),
                      mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                       mAttachInfo.mOutsets, mInputChannel);
           }
    
    

    这里最终调用了mWindowSession.addToDisplay()进行进程间通信,最终会调用到WMS到addView(),并将自身也就是Session传进去。每个应用程序都会对应一个Session,WMS会用ArrayList来保存这些Session。剩下的工作就会交由WMS处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序。

  • Activity的添加过程

    无论哪种窗口,它的添加过程在WMS处理部分中基本是类似的,只不过会在权限和窗口显示次序等方面会又些不同。但是在WindowManager处理部分会有所不同。

    当Activity启动时会调用ActivityThread的handleResumeActivity():

        @Override
        public void handleResumeActivity() {
    
                  //这里会调用到Activity的attach(),会初始化WindowManager
            final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
                          //获取初始化好的WindowManager
                ViewManager wm = a.getWindowManager();
                      //wm添加decorView
                wm.addView(decor, l);
        }
    
    

    可以看到,最后这里将decorView添加到了Activity里,之后的流程和status一样。

  • Window到更新过程

    Window的更新方法是调用upDateViewLayout(),同样是看WindowManagerGlobal.upDateViewLayout(),看代码:

           public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    
               final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
                       //将更新的参数设置进view中
               view.setLayoutParams(wparams);
       
               synchronized (mLock) {
                      //找到要更新的窗口的索引
                   int index = findViewLocked(view, true);
                   //根据所有的到它的ViewRootImpl
                   ViewRootImpl root = mRoots.get(index);
                   //更新布局列表参数
                   mParams.remove(index);
                   mParams.add(index, wparams);
                   //将更新好的参数设置到ViewRootImpl中
                   root.setLayoutParams(wparams, false);
               }
           }
    
    

    可以看到,这里首先找到要更新的窗口的索引,然后根据索引找到它的ViewRootImpl,最后更新参数后通过ViewRootImpl.setLayoutParams()进行设置,看代码:

           void setLayoutParams() {
                   scheduleTraversals();
           }
    
    

    这里调用了scheduleTraversals(),看代码:

           void scheduleTraversals() {
              //发起一个回调
            mChoreographer.postCallback(
                           Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    
           }
    
    

    这里通过Choreographer发起一个回调,这个回调在下一帧被渲染时执行它的run(),那么再看它的run():

           final class TraversalRunnable implements Runnable {
               @Override
               public void run() {
                   doTraversal();
               }
           }
    

    这里执行了doTraversal(),继续看:

           void doTraversal() {
            //开始绘制流程
            performTraversals();
          }
    

    这里可以看到,调用performTraversals(),这个方法最终会调用performMeasure(),performLayout(),performDraw()完成整个绘制流程。

你可能感兴趣的:(Android中WindowManager的理解)