View-Window-Surface

View-Window-Surface

前言

本文打算从Root View的建立出发,呈现ViewWindowSurface之间的联系。
从而厘清这三者之间的不同,加深对Android Graphic系统的理解

V-W-S简介

  • View:android中最基本的UI组件,会占据屏幕上的一席之地并可以处理一些基本事件。

    This class represents the basic building block for user interface components. A View
    occupies a rectangular area on the screen and is responsible for drawing and
    event handling. View is the base class for widgets, which are
    used to create interactive UI components (buttons, text fields, etc.).

  • Window:Window是top View的容器,这里的top view我们一般都是说ViewRoot。

    Window的概念在Android中比较抽象,广义上,被认为是一个App对应了一个window,狭义上,Window实际又对应了
    ViewRoot,对于WindowManagerService来说,如何呈现/管理App端的显示,都是通过Window来实现的

  • Surface:Surface可以认为是一块提供绘制的buffer,所有View的修改,最终都会被统一反馈到Surface上。

    每个ViewRoot都会有一个真实的surface变量,View的所有操作会反馈到这块Surface。这块Surface也即SurfaceFlinger
    的某一个composer对象,因此只有操作到了ViewRoot中的Surface,屏幕上才会有变化
    注意:此处的Surface并不是SurfaceView,SurfaceView本质上还是一个View,注意区分

RTFSC

为了更深刻的理解这个问题,本文打算从ViewRoot的建立一路讲述,一直到三者的联系完全建立未知。

setContentView

  • 对于setContentView相信大家都不会陌生,一般都是传入一个Resouce ID,这样整个Activity的视图就建立了。
    image-20221010154450298

  • ok,对于Activity来说,其实是透过getWindow,来做setContentView,那么我们看看对应的Window是谁。
    image-20221010154519287

  • mWindow是Activity类内部的一个私有变量,顺藤摸瓜,我们看一下mWindow是从哪里被new出来的。

    如果你熟悉Activity的创建过程,应该就知道了,mWindow其实是在performLaunchActivity的时候,调用Activity.attach的时候创建的。
    image-20221010154536349

  • 所以,下面我们需要去看看PhoneWindow.setContentView

    顺便插一句,PhoneWindow这个类比较重要,而它本身是extends Window
    image-20221010154551681

    可以看到对于传入的layoutResID,最终会通过inflate创建出对应的View
    但是整个过程中并没有看到ViewRoot,所以我们还需要看看划红线的installDecor

  • 通过上图可以看到,当且仅当mContentParent == null的时候才会走进去,那么mContentParent是哪里赋值的呢?
    image-20221010154608645

    这里又平白无故多出了个mDecor,什么是private DecorView mDecor
    我们先通过视图查看神器hierarchyviewer搂一眼,先看看第一个initXXX
    image-20221010154624344
    显然,DecorView是一个Root View,如果这个不清楚..那么请看下图
    image-20221010154643857

  • 让我们来聚焦到:mDecor = generateDecor(-1);
    image-20221010154700783

  • 直接了当,继续往下看看new DecorView

    首先,DecorView是extends自Framelayout,从本质上,DecorView是一个容器Viewgroup,也是一个View
    image-20221010154746613

    再来看看真正的new DecorView
    image-20221010154805704

    通过setWindow函数,会建立起PhoneWindowDecorView建立联系,因此,DecorView中就会持有PhoneWindow的引用

    • 至此,整个mDecor = generateDecor(-1);的流程就走完了,简单来说
      • DecorView是一个ViewGrouphierarchyviewer中也可以看出来,整个DecorView是视图的Root
      • 建立了PhoneWindowDecorView
  • 我们走完了generateDecor,继续往下走,来到mContentParent = generateLayout(mDecor);
    image-20221010154827015

    整个generateLayout分为4步

    • 根据一系列判定条件选择合适的layoutResource
    • 通过对应的layoutResource进行inflate
    • 透过整个ViewTree获取contentParent
    • 这个contentParent,就是上面提到的mContentParent

    其中比较重要的是步骤二和步骤三,也即:

    • 透过整个ViewTree获取contentParent

      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    • 这个contentParent,就是上面提到的mContentParent

      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

    我们直接看一下mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    image-20221010154845132

    • 其中createDocrCaptionView的部分可以略过,根据原生的代码,这一块主要是给FreeForm来使用的
      如下图所示:
      image-20221010154901523
    • inflate也没有什么可说的,第二参数为null,表示无默认Parent View
    • addView亦是常规操作,显然是把刚才inflate出来的root作为child viewadd到DecorView
    • 最后一波mContentRoot = (ViewGroup) root;整个操作结束

    我们回头再看一下DecorView三剑客
    image-20221010154922681

    • mWindow:在DecorView初始化的时候就已经建立了联系
    • mContentRoot:通过feature选择的最恰当layoutResource,进而通过inflate创建的View
    • mDecorCaptionViewFreeForm的“action bar”

    再来看看ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

    image-20221010154938425

    image-20221010154953028

    image-20221010155008139

    image-20221010155034605
    以上4支api完成了整个findViewById的操作,通过之前的认知,整个mDocrWindow/ViewTree的root
    而对应的ID_ANDROID_CONTENT,则是整个ViewTree的Root下的第一个View,而这个ID_ANDROID_CONTENT
    image-20221010155048302
    这个id,确实也是在frameworks res里面会用到,也就是那些默认的layout
    image-20221010155103838
    到这里,我们把mContentParent = generateLayout(mDecor);走完了,简单总结一下:

    • 通过generateLayout,基于mDecor又添加了一个子View,ID:ID_ANDROID_CONTENT(视情况不同可能为CaptionView)
    • 最终mContentParent永远都是ID:ID_ANDROID_CONTENTView
  • 走完了上面一长串流程,其实是在建立mDecor/PhoneWindow/mContentParent之间的联系,而我们通过setContentView传入的那个Resource ID,压根都还没有用到。
    重新看这一段,整个installDecor就完成了。
    image-20221010155121891

  • 下面才是用到了layoutResID,通过inflate,把这个layoutResID对应的View放到mContentParent

    那么这个mContentParent是谁呢?再回头看,其实就是generateLayout中的return contentParent,也就是对应的ID:ID_ANDROID_CONTENT

因此,整个setContentView的所有逻辑就走完了。

  • 通过installDecor,把PhoneWindow/DecorView/mContentRoot建立起联系
  • app端真正传入的ResouceID,会通过inflate出一个View,把它加到mContentRoot也即ID:ID_ANDROID_CONTENT

小结:

对于app端来说setContentView是整个ViewTree建立的入口,也是好多Demo中onCreate必撸的代码。
当了解了setContentView传入的resID最终会变成哪个View,变成谁的Child View,厘清这一点对于debug很有必要。

UML两张流

UML类图

比较重要的是三个类:ActivityPhoneWindowDecorView
image-20221010155143869

UML时序图

对应的流程图:
img

View与Window-MnagerService

先前的setContentView帮助我们在app内部建立了一套完整的ViewTree
但是自始至终,我们都没有发现App与System_Server之间发生任何的关系,通过我们认识,Window只有被add到了WindowManagerService之后才会有机会被显示出来。

一点点基础知识

  • 顺着这个思路,我们继续往下走,这边直接来到ViewRootImpl的构造函数处:
    image-20221010155222903
  • 以上Call Stack是既成事实的,但是我们仍旧要看一下传入的各个参数:
    img

    这里的r,其实是Activity
    对应的r.window,就是PhoneWindow
    r.window.getDecorView(),其实就是对应的DecorView
    因此整个ViewManager最后会把decor添加进去

  • 根据一系列的继承关系:WindowManagerImpl -|> WindowManager -|> ViewManager,所以就来到了WindowManagerImpl
    img

    这里的mGlobal又是一个典型的桥接模式

View / Window / WMS

  • mGlobaladdView中,就会去创建ViewRootImpl,从而建立起与WMS的整个联系,也就是串起了ViewWindow
    img

  • ViewRootImpl的初始化,是一个非常重要的过程:
    img

    • 对于第一步,我们先来看看mWindowSession = WindowManagerGlobal.getWindowSession();

      其中mWindowSession是一个IPC的Proxy对象:IWindowSession
      img

      对应的native实体,其实是Session,而这个Session则是WindowState中的一个的对象:
      image-20221010155411894
      image-20221010155432163
      WindowState的话,则是WindowManagerManager中重要的对象(基本上可以认为一个client端的window,就是WMS端的一个WindowState

    • 再来看看第二步:mWindow = new W(this);,对于这个W,它继承自IWindow.Stub
      image-20221010155448736

      这里引入出了一个IWindow的概念,由于在App端我们持有的是Stub,因此,在WMS那边的话其实就是对应的Proxy

    • 整个ViewRootImpl的初始化,我们有两个重要对象mWindowSession以及mWindow,我们先

      • mWindowSession:是继承自IWindowSession ,也即App端,是一个Client
      • mWindow:是继承自IWindow.Stub,也即App端,是一个Server
  • 完成了ViewRootImpl的初始化,我们继续往下走:root.setView(view, wparams, panelParentView);

    这里的root,其实就是ViewRootImpl,因此这边其实是调用了ViewRootImpl.setView
    image-20221010155504019

    • 整个setView的过程,其实是在建立App端与Server的联系,也即App与WMS
      image-20221010155516310

    这里的requestLayout其实也有其特定作用,但本文先不分析了,我们直接看mWindowSession.addToDisplay

  • 根据我们对AIDL的认识,可以知道这边会直接跑到Server端运行,而之前也知道,对应的Server端其实是Session对象,因此这边的mWindowSession.addToDisplay,就会走到Session.addToDisplay
    image-20221010155528692

    • 其中,传入的第一个参数window,在App端其实就是IWindow.Stub实例mWindow
    • Session中的mService,实际是WindowManagerService
  • 因此,Session.addToDisplay最终就跑到了WindowManagerService.addToDisplay
    img
    整个addWindow的流程,针对当前的流程来梳理,我们需要看上述的第一步和第二步。

    • 其中第一步,session对应了App端的mWindowSessionclient对应的则是App端的mWindow
      image-20221010155559836

      这里的mWinAnimator,在WMS里面往往是用来做各类动画的基础

    • 第二步,win.attach
      img

      这里的mSession是之前在构造函数中赋值的Session实例。
      img
      通过这个windowAddedLockedSessionmService.mSessions.add(this)与WMS建立了联系
      同样:mSurfaceSession = new SurfaceSession();这个地方,Session才真正与SurfaceFlinger建立了联系
      image-20221010155639910

小结

ViewRootImpl的初始化过程中,会带出mWindowSession以及mWindow
其中,mWindowSessionISession在App端的Proxy,而mWindow则是IWindow在App端的ISession
因此,两者的地位和作用也是不同的,mWindowSession是用来与WMS做通讯的,而mWindow则是WMS用来回调App的。

UML两张流

UML类图

img
点评一下这张令人头大的UML蜘蛛图:

  • Activity中会持有mWindow,它是PhoneWindow的实例
  • PhoneWindow继承自Window
  • PhoneWindow中,会持有DecorView;同样DecorView也会持有PhoneWindow的实例
  • DecorView集成自FrameLayout,这一路是从ViewGroupViewParentViewManager下来的。

上述内容是之前setContentView中的东西

  • ViewRootImplWindowManagerGlobal所持有
  • WindowManagerGlobal通过桥接模式,被WindowManagerImpl所持有,而WindowManagerImpl实现了WindowManager的接口
  • ViewRootImpl会持有IWindowSession的实例mWindowSession
  • IWindowSession继承自IInterface,它是一个Client端,运行在App进程,System_Server是Server端
  • ViewRootImpl会持有ViewRoot.W的实例mWindow
  • ViewRootImpl.W是继承自IWindow.Stub,它是一个Server端,运行在App进程,System_Server是Client端

UML时序图

img