06.Android应用窗口
目录介绍
- 01.Android组件UI实现
- 1.1 组件的UI实现介绍
- 1.2 Activity组件类关系
- 1.3 Window类的实现
- 1.4 ViewRoot类的实现
- 02.上下文环境创建过程
- 2.1 窗口运行环境介绍
- 2.2 Context创建流程
- 2.3 运行环境总结下
- 03.窗口Window创建
- 3.1 Activity和Window
- 3.2 窗口Window创建过程
- 3.3 Window显示过程
- 3.4 Dialog中Window创建
- 04.视图View创建
- 4.1 视图View的介绍
- 4.2 DecorView类实现
- 4.3 窗口视图与ViewRoot
- 4.4 窗口视图创建过程
- 05.应用窗口与WMS
- 5.1
- 06.Surface创建过程
- 6.1
00.问题答疑思考
- Activity、PhoneWindow、DecorView、ViewRootImpl 之间的关系?四者的创建时机?
01.Android组件UI实现
1.1 组件的UI实现介绍
- Activity组件的UI实现需要与WindowManagerService服务和SurfaceFlinger服务进行交互。
- Activity组件在启动完成后,会通过一个类型为Session的Binder对象来请求WindowManagerService为它创建一个类型为WindowState的对象,用来描述它的窗口状态。
- Android应用程序会通过一个类型为Client的Binder对象来请求SurfaceFlinger服务为它创建一个类型为Layer的对象,用来描述它的窗口数据。
- Activity组件与WindowManagerService服务和SurfaceFlinger服务的交互模型
image
1.2 Activity组件类关系
- Activity组件的类关系图
image
- 从启动到Activity创建Window的整体流程
image
- Activity创建Surface流程
image
- Activity类与Context关系
- Activity类是从ContextThemeWrapper类继承下来的,而ContextThemeWrapper类又是从ContextWrapper类继承下来的,最后ContextWrapper类又继承了Context类。
- 这个ContextImpl对象首先是通过调用Activity类的成员函数attach传递到Activity组件内部,接着再依次通过调用父类ContextThemeWrapper和ContextWrapper的成员函数attachBaseContext来分别保存在它们的成员变量mBase中。
- 因此,ContextThemeWrapper和ContextWrapper类的成员变量mBase指向的实际上是一个ContextImpl对象。
- Activity启动是如何和Context交互的
ActivityThread#performLaunchActivity(),创建Activity类型上下文Context然后创建activity对象 ActivityThread#createBaseContextForActivity(),通过ContextImpl静态方法创建该对象 ContextImpl#createActivityContext(),通过new去新建一个ContextImpl对象,并且做初始化配置操作
1.3 Window类的实现
- Activity组件创建一个PhoneWindow
image
- PhoneWindow创建过程
ActivityThread#performLaunchActivity(),通过反射的机制创建的Activity,并调用了Activity的attach方法 Activity#attach(),在attach方法这里初始化了一些Activity的成员变量,最重要是创建PhoneWindow对象
- PhoneWindow类的实现说明
- PhoneWindow类有两个重要的成员变量mDecor和mContentParent,它们的类型分别DecorView和ViewGroup。
- 其中,成员变量mDecor是用描述自己的窗口视图,而成员变量mContentParent用来描述视图内容的父窗口。
- DecorView类继承了FrameLayout类
- DecorView类继承了FrameLayout类,而FrameLayout类又继承了ViewGroup类,最后ViewGroup类又继承了View类。View类有一个成员函数draw,它是用来绘制应用程序窗口的UI的。
- DecorView类、FrameLayout类和ViewGroup类都重写了父类的成员函数draw,这样,它们就都可以定制自己的UI。
- DecorView类所描述的应用程序窗口视图是否需要重新绘制是由另外一个类ViewRoot来控制的。
- 系统在启动一个Activity组件的过程中,会为这个Activity组件创建一个ViewRoot对象,同时还会将前面为这个Activity组件所创建的一个PhoneWindow对象的成员变量mDecor所描述的一个视图(DecorView)保存在这个ViewRoot对象的成员变量mView中。
- 这个ViewRoot对象就可以通过调用它的成员变量mView的所描述的一个DecorView的成员函数draw来绘制一个Activity组件的UI了。
1.4 ViewRoot类的实现
- ViewRoot类的实现图
image - ViewRoot类的作用是非常大的,它除了用来控制一个Activity组件的UI绘制之外,还负责接收Activity组件的IO输入事件,例如,键盘事件
- ViewRoot类的创建过程
WindowManagerImpl#addView(),在这个类中可以看到调用了mGlobal.addView(),而mGlobal是WindowManagerGlobal的对象。 WindowManagerGlobal#addView(),这里面逻辑很核心,创建ViewRootImpl对象,这个是布局渲染的核心类 WindowManagerGlobal#addView#root.setView(),实现了view与root的关联,这个root是ViewRootImpl对象
02.上下文环境创建过程
2.1 窗口运行环境介绍
- Android应用程序窗口在运行的过程中,需要访问一些特定的资源或者类。
- 这些特定的资源或者类构成了Android应用程序的运行上下文环境,Android应用程序窗口可以通过一个Context接口来访问它,这个Context接口也是我们在开发应用程序时经常碰到的。
- Android应用程序窗口的运行上下文环境是通过ContextImpl类来描述的,即每一个Activity组件都关联有一个ContextImpl对象。
image - Activity组件通过其父类ContextThemeWrapper和ContextWrapper的成员变量mBase来引用了一个ContextImpl对象。
- 这样,Activity组件以后就可以通过这个ContextImpl对象来执行一些具体的操作,例如,启动Service组件、注册广播接收者和启动Content Provider组件等操作。
2.2 Context创建流程
- Context创建流程
- 通过ActivityThread类的成员函数performLaunchActivity在应用程序进程中创建一个Activity实例,并且为它设置运行上下文环境,即为它创建一个ContextImpl对象。
image
- 比较重要的流程介绍
ActivityThread.handleLaunchActivity() ActivityThread.performLaunchActivity() mInstrumentation.newActivity 使用反射创建Activity activity.attach 这里会调用Activity中attach方法 Activity.attach() mWindow = new PhoneWindow 这里创建所属的PhoneWindow mWindow.setWindowManager 设置并获取 WindowManagerImpl 对象
- Android应用程序窗口的运行上下文环境的创建过程
- 第一步:ActivityThread#performLaunchActivity(),主要看mInstrumentation.newActivity()的过程。Instrumentation类是用来记录应用程序与系统的交互过程的
- 要启动的Activity组件的类名保存在变量component。有了这个类名之后,函数就可以调用ActivityThread类的成员变量mInstrumentation所描述一个Instrumentation对象的成员函数newActivity来创建一个Activity组件实例了,并且保存变量activity中。
- 第二步:ActivityThread#createBaseContextForActivity,这里是创建ContextImpl的步骤
- 这里最终会调用到ContextImpl#createActivityContext,通过new创建该impl对象,且进行初始化附值操作。
- 第三步:Instrumentation#newActivity(),这个是创建activity的过程。
- 这里是通过ClassLoader去load具体的要加载的类的className,得到一个Class对象。
- 由于className描述的是一个Activity子类,因此,当函数调用前面得到的Class对象的成员函数newInstance的时候,就会创建一个Activity子类实例。
- 第四步:new Activity,第二步通过反射创建activity对象,创建对象是必须通过构造函数创建的
- 一般来说,一个类的构造函数是用来初始化该类的实例的,系统为Activity类提供的默认构造函数什么也不做,也就是说,Activity类实例在创建的时候,还没有执行实质的初始化工作。
- 第五步:appContext.setOuterContext(),还是在performLaunchActivity方法里面,看看做了什么
- 即ActivityThread类的成员函数performLaunchActivity中,接下来就会调用ContextImpl类的成员函数setOuterContext来设置前面所创建一个ContextImpl对象所关联的一个Activity组件,即正在启动的Activity组件。
- 一个ContextImpl对象所关联的一个Activity组件是通过调用ContextImpl类的成员函数setOuterContext来设置的。
- setOuterContext只是简单地将它保存在成员变量mContext中,以表明当前正在处理的一个ContextImpl对象是用来描述一个Activity组件的运行上下文环境的。
- 第六步:activity.attach(),这里是真正进入到Activity初始化的过程
- 初始化一个Activity组件实例需要一个Application对象app、一个ContextImpl对象appContext以及一个Configuration对象config,它们分别用来描述该Activity组件实例的应用程序信息、运行上下文环境以及配置信息。
- 最开始是调用Activity#attachBaseContext()方法,这个我们看下面的第七步。
- 接着创建了一个PhoneWindow,并且保存在Activity类的成员变量mWindow中。这个PhoneWindow是用来描述当前正在启动的应用程序窗口的。
- 这个应用程序窗口在运行的过程中,会接收到一些事件,例如,键盘、触摸屏事件等,这些事件需要转发给与它所关联的Activity组件处理,这个转发操作是通过一个Window.Callback接口来实现的。
- 参数info指向的是一个ActivityInfo对象,用来描述当前正在启动的Activity组件的信息。其中,这个ActivityInfo对象的成员变量softInputMode用来描述当前正在启动的一个Activity组件是否接受软键盘输入。
- 第七步:ContextThemeWrapper.attachBaseContext(),接着第六步往下分析
- 由调用Activity#attachBaseContext()方法分析,最终会调用到ContextThemeWrapper#attachBaseConext。
- ContextThemeWrapper类用来维护一个应用程序窗口的主题,而用来描述这个应用程序窗口的运行上下文环境的一个ContextImpl对象就保存在ContextThemeWrapper类的成员函数mBase中。
- 第八步:ContextWrapper.attachBaseContext(),,接着第七步往下分析
- ContextWrapper类只是一个代理类,它只是简单地封装了对其成员变量mBase所描述的一个Context对象的操作。
- ContextWrapper类的成员函数attachBaseContext的实现很简单,它只是将参数base所描述的一个ContextImpl对象保存在成员变量mBase中。这样,ContextWrapper类就可以将它的功能交给ContextImpl类来具体实现。
- 第九步:Instrumentation#callActivityOnCreate(),主要是执行activity的生命周期onCreate
- 函数主要就是调用当前正在启动的Activity组件的成员函数onCreate,用来通知它已经成功地创建和启动完成了。
- 第十步:Activity#onCreate(),这里会回调Activity的方法,也是开发的入口
- 一般来说,我们都是通过定义一个Activity子类来实现一个Activity组件的。重写父类Activity的某些成员函数的时候,必须要回调父类Activity的这些成员函数。
- 例如,当Activity子类在重写父类Activity的成员函数onCreate时,就必须回调父类Activity的成员函数onCreate。这些成员函数被回调了之后,Activity类就会将其成员变量mCalled的值设置为true。这样,Activity类就可以通过其成员变量mCalled来检查其子类在重写它的某些成员函数时,是否正确地回调了父类的这些成员函数。
- 第一步:ActivityThread#performLaunchActivity(),主要看mInstrumentation.newActivity()的过程。Instrumentation类是用来记录应用程序与系统的交互过程的
2.3 运行环境总结下
- 一个Activity组件的创建过程,以及它的运行上下文环境的创建过程。这个过程比较简单,我们是从中获得以下三点信息:
- 一个Android应用窗口的运行上下文环境是使用一个ContextImpl对象来描述的,这个ContextImpl对象会分别保存在Activity类的父类ContextThemeWrapper和ContextWrapper的成员变量mBase中,即ContextThemeWrapper类和ContextWrapper类的成员变量mBase指向的是一个ContextImpl对象。
- Activity组件在创建过程中,即在它的成员函数attach被调用的时候,会创建一个PhoneWindow对象,并且保存在成员变量mWindow中,用来描述一个具体的Android应用程序窗口。
- Activity组件在创建的最后,即在它的子类所重写的成员函数onCreate中,会调用父类Activity的成员函数setContentView来创建一个Android应用程序窗口的视图。
03.窗口Window创建
3.1 Activity和Window
- 在PHONE平台上,与Activity组件所关联的窗口对象的实际类型为PhoneWindow,后者是从Window类继承下来的。
image
- Window和PhoneWindow的类关系图。
image
- Window创建流程图如下所示
image
3.2 窗口Window创建过程
- Activity组件所关联的一个PhoneWindow对象是从Activity类的成员函数attach中创建的。
image
- Android应用程序窗口创建过程的源码流程分析
- Activity#attach(),在attach方法中创建了核心的Window窗口
- 有了这个类型为PhoneWindow的应用程序窗口,函数接下来还会调用它的成员函数setCallback、setSoftInputMode和setWindowManager来设置窗口回调接口、软键盘输入区域的显示模式和本地窗口管理器。
- PhoneWindow#(),在构造函数中获取布局解析器
- 首先调用父类Window的构造函数来执行一些初始化操作,接着再调用LayoutInflater的静态成员函数from创建一个LayoutInflater实例,并且保存在成员变量mLayoutInflater中。
- PhoneWindow类以后就可以通过成员变量mLayoutInflater来创建应用程序窗口的视图,这个视图使用类型为DecorView的成员变量mDecor来描述。
- PhoneWindow类还有另外一个类型为ViewGroup的成员变量mContentParent,用来描述一个视图容器,这个容器存放的就是成员变量mDecor所描述的视图的内容,不过这个容器也有可能指向的是mDecor本身。
- Activity#attach()中mWindow.setCallback(this),这个是设置接口与Activity关联
- 正在启动的Activity组件会将它所实现的一个Callback接口设置到与它所关联的一个PhoneWindow对象的父类Window的成员变量mCallback中去
- 这样当这个PhoneWindow对象接收到系统给它分发的IO输入事件。例如,键盘和触摸屏事件,转发给与它所关联的Activity组件处理
- Activity#attach()中mWindow.setSoftInputMode(),这个是设置输入键盘和Activity关联
- PhoneWindow对象从父类Window继承下来的成员函数setSoftInputMode来设置应用程序窗口的软键盘输入区域的显示模式
- Activity#attach()中mWindow.setWindowManager(),这个是本地窗口管理器和Activity关联
- 调用前面所创建的PhoneWindow对象从父类Window继承下来的成员函数setWindowManager来设置应用程序窗口的本地窗口管理器
- 参数appToken用来描述当前正在处理的窗口是与哪一个Activity组件关联的,它是一个Binder代理对象,引用了在ActivityManagerService这一侧所创建的一个类型为ActivityRecord的Binder本地对象。
- 参数appName用来描述当前正在处理的窗口所关联的Activity组件的名称,这个名称会被保存在Window类的成员变量mAppName中。
- 参数wm用来描述一个窗口管理器。从前面的调用过程可以知道, 这里传进来的参数wm的值等于null,因此,函数首先会调用WindowManagerImpl类的静态成员函数getDefault来获得一个默认的窗口管理器。有了这个窗口管理器之后,函数接着再使用它来创建一个本地窗口管理器,即一个LocalWindowManager对象,用来维护当前正在处理的应用程序窗口。
- Activity#attach(),在attach方法中创建了核心的Window窗口
- 分析完成一个Activity组件所关联的应用程序窗口对象的创建过程了。从分析的过程可以知道:
- 一个Activity组件所关联的应用程序窗口对象的类型为PhoneWindow。
- 这个类型为PhoneWindow的应用程序窗口是通过一个类型为LocalWindowManager的本地窗口管理器来维护的。
- 这个类型为LocalWindowManager的本地窗口管理器又是通过一个类型为WindowManagerImpl的窗口管理器来维护应用程序窗口的。
- 这个类型为PhoneWindow的应用程序窗口内部有一个类型为DecorView的视图对象,这个视图对象才是真正用来描述一个Activity组件的UI的。
3.3 Window显示过程
- Window显示流程图如下所示。主要是讲解activity中Window显示过程
image
- 大概重要流程介绍
ActivityThread.handleResumeActivity() ActivityThread.performResumeActivity() 执行到 onResume() r.activity.makeVisible() 添加视图 Activity.makeVisible()
wm.addView 将decorView添加到Window窗口上 mDecor.setVisibility(View.VISIBLE) 设置mDecor可见 - WindowManager的addView流程
- WindowManager 的 addView 的具体实现在 WindowManagerImpl 中,而 WindowManagerImpl 的 addView 又会调用 WindowManagerGlobal.addView()。
- WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) { ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); root.setView(view, wparams, panelParentView); }
- 这个过程创建一个 ViewRootImpl,并将之前创建的 DecoView 作为参数传入,以后 DecoView 的事件都由 ViewRootImpl 来管理了,比如,DecoView 上添加 View,删除 View。ViewRootImpl 实现了 ViewParent 这个接口,这个接口最常见的一个方法是 requestLayout()。
- ViewRootImpl 是个 ViewParent,在 DecoView 添加的 View 时,就会将 View 中的 ViewParent 设为 DecoView 所在的 ViewRootImpl,View 的 ViewParent 相同时,理解为这些 View 在一个 View 链上。所以每当调用 View 的 requestLayout()时,其实是调用到 ViewRootImpl,ViewRootImpl 会控制整个事件的流程。可以看出一个 ViewRootImpl 对添加到 DecoView 的所有 View 进行事件管理。
3.4 Dialog中Window创建
- Dialog的Window创建过程?
- 创建Window——同样是通过PolicyManager的makeNewWindow方法完成,与Activity创建过程一致
- 初始化DecorView并将Dialog的视图添加到DecorView中——和Activity一致(setContentView),同样是通过Window.setContentView() 来实现。
- 将DecorView添加到Window中并显示——在Dialog的show方法中,通过WindowManager将DecorView添加到Window中(mWindowManager.addView(mDecor, 1))
- Dialog关闭时会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
- Dialog必须采用Activity的Context,因为有应用token(Application的Context没有应用token),也可以将Dialog的Window通过type设置为系统Window就不再需要token。
- 为什么Dialog不能用Application的Context?
- Dialog本身的Token为null,在初始化时如果是使用Application或者Service的Context,在获取到WindowManager时,获取到的token依然是null。
- Dialog如果采用Activity的Context,获取到的WindowManager是在activity.attach()方法中创建,token指向了activity的token。
- 因为通过Application和Service的Context将无法获取到Token从而导致失败。
- 将DecorView添加到Window中显示
- 和Activity一样,都是在自身要出现在前台时才会将添加Window。
- Dialog.show() 方法:完成DecorView的显示。
- WindowManager.remoteViewImmediate() 方法:当Dialog被dismiss时移除DecorView。
04.视图View创建
4.1 视图View的介绍
- 每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。
- 每一个应用程序窗口内部又包含有一个View对象,用来描述应用程序窗口的视图。应用程序窗口视图是真正用来实现UI内容和布局的,每一个Activity组件的UI内容和布局都是通过与其所关联的一个Window对象的内部的一个View对象来实现的。
- 应用程序窗口内部所包含的视图对象的实际类型为DecorView。DecorView类继承了View类,是作为容器(ViewGroup)来使用的。
4.2 DecorView类实现
- DecorView类的实现的流程图
image
4.3 窗口视图与ViewRoot
- 窗口视图与ViewRoot流程图
- 每一个应用程序窗口的视图对象都有一个关联的ViewRoot对象,这些关联关系是由窗口管理器来维护的。
image
- 简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责:
- 1.负责为应用程序窗口视图创建Surface。
- 2.配合WindowManagerService来管理系统的应用程序窗口。
- 3.负责管理、布局和渲染应用程序窗口视图的UI。
- 应用程序窗口的视图对象及其所关联的ViewRoot对象是什么时候开始创建的呢?
- Activity组件在启动的时候,系统会为它创建窗口对象(Window),同时,系统也会为这个窗口对象创建视图对象。
- 另一方面,当Activity组件被激活的时候,系统如果发现与它的应用程序窗口视图对象所关联的ViewRoot对象还没有创建,那么就会先创建这个ViewRoot对象,以便接下来可以将它的UI渲染出来。
4.4 窗口视图创建过程
- 分析应用程序窗口的视图对象及其所关联的ViewRoot对象的创建过程
image
- 应用程序窗口视图的创建过程
- Activity组件在启动的过程中,会调用ActivityThread类的成员函数handleLaunchActivity,用来创建以及首次激活Activity组件,因此,接下来就从这个函数开始,具体分析应用程序窗口的视图对象及其所关联的ViewRoot对象的创建过程。
ActivityThread#handleLaunchActivity(),调用ActivityThread类的成员函数performLaunchActivity来创建要启动的Activity组件。 ActivityThread#performLaunchActivity(),它主要就是创建一个Activity组件实例,并且调用这个Activity组件实例的成员函数onCreate来让其执行一些自定义的初始化工作。 Activity#onCreate(),在实现一个Activity组件的时候,一般都会重写成员函数onCreate,以便可以执行一些自定义的初始化工作,其中就包含初始化UI的工作。 Activity#setContentView(),首先调用另外一个成员函数getWindow来获得成员变量mWindow(指向PhoneWindow)所描述的一个窗口对象,接着再调用这个窗口对象的成员函数setContentView来执行创建应用程序窗口视图对象的工作。 PhoneWindow#setContentView(),成员变量mContentParent(指DecorView的视图对象),当它等于null,就会调用成员函数installDecor来创建应用程序窗口视图对象。最后还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件,它的视图内容发生改变。 PhoneWindow#installDecor(),当mDecor等于null,调用成员函数generateDecor创建一个DecorView对象,并且保存在PhoneWindow类的成员变量mDecor中。
- ActivityThread类的成员函数handleLaunchActivity中,接下来就会调用ActivityThread类的另外一个成员函数handleResumeActivity来激活正在启动的Activity组件。
ActivityThread#handleResumeActivity(),首先调用另外一个成员函数performResumeActivity来通知Activity组件,它要被激活了,即会导致Activity组件的成员函数onResume被调用。 handleResumeActivity#r.activity.getWindow(),getWindow返回给调用者的是一个PhoneWindow对象 handleResumeActivity#r.activity.getDecorView(),通过getDecorView来获得当前正在激活的Activity组件所关联的一个应用程序窗口视图对象 handleResumeActivity#a.getWindowManager(),调用当前正在激活的Activity组件的成员函数getWindowManager来获得一个本地窗口管理器。 handleResumeActivity#wm.addView(decor, l),addView来为当前正在激活的Activity组件的应用程序窗口视图对象关联一个ViewRoot对象。 WindowManagerGlobal#addView(),这个里面会创建ViewRootImpl对象,一个View对象在与一个ViewRoot对象关联的同时,还会关联一个WindowManager.LayoutParams对象,这个WindowManager.LayoutParams对象是用来描述应用程序窗口视图的布局属性的。 WindowManagerGlobal#addView#root.setView,将这个View对view象和这个WindowManager.LayoutParams对象,以及变量panelParentView所描述的一个父应用程序窗视图对象,保存在这个ViewRoot对象root的内部 ViewRootImpl#setView(),调用requestLayout来请求对应用程序窗口视图的UI作第一次布局。调用静态成员变量sWindowSession所描述的一个类型为Session的Binder代理对象的成员函数add来请求WindowManagerService增加一个WindowState对象,以便可以用来描述当前正在处理的一个ViewRoot所关联的一个应用程序窗口。
05.应用窗口与WMS
- https://www.kancloud.cn/alex_wsc/androids/473773
06.Surface创建过程
- https://www.kancloud.cn/alex_wsc/androids/473774