Activity窗口及视图的创建

∥☆過路亽.° 提交于 2020-03-12 12:41:06

Activity是Android应用程序的载体,允许用户在其上创建一个用户界面,并提供用户处理事件的API。但它并不是显示视图,实际上Activity调用了PhoneWindow的setContentView()方法,然后加载视图,将视图放到这个Window上,而Activity其实创建的时候初始化的是Window(PhoneWindow)。Activity其实是个控制单元,即可视的人机交互界面。打个比方,Activity是一个工人,它来控制Window;Window是一面显示屏,用来显示信息;View就是要显示在显示屏上的信息,这些View都是层层重叠在一起(通过infalte()和addView())放到Window显示屏上的。而LayoutInfalter就是用来生成View的一个工具,XML布局文件就是用来生成View的原料。
Activity类的成员变量mWindow的类型为Window,它用来描述一个应用程序窗口。通过这个成员变量,每一个Activity组件就都会有一个对应的Window对象,即一个对应的应用程序窗口。
Window类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。
Window类有一个类型为Context的成员变量mContext,它指向的是一个Activity对象。当系统为一个Activity组件创建一个对应的Window对象时,就会将这个Activity组件的Context接口保存在这个对应的Window对象的mContext中。这样,一个Window对象就可以通过它的成员变量mContext来访问它所描述的Activity组件的资源。
Window类还有一个类型为Window.Callback的成员变量mCallback。它和成员变量mContext一样,都是指向同一个Activity对象,因为Activity类实现了Window.Callback接口的。当系统为一个Activity组件创建一个对应的Window对象时,就会将这个Activity组件所实现的Window.Callback接口通过Window类的成员函数setCallback保存在对应的Window对象的成员变量mCallback。这样,一个Window对象就可以通过它的成员变量mCallback来将一些事件交给与它所对应的Activity组件来处理,例如,将接收的键盘事件交给对应的Activity组件来处理。
在这里插入图片描述
在这里插入图片描述
PhoneWindow类继承于Window类,是Window类的具体实现,即可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
PhoneWindow类有两个重要的成员变量mDecor和mContentParent,它们的类型分别DecorView和ViewGroup。其中,成员变量mDecor用来描述自己的窗口视图,而成员变量mContentParent用来描述视图内容的父窗口。
PhoneWindow类的构造函数很简单,它首先调用父类Window的构造函数来执行一些初始化操作,接着再调用LayoutInflater的静态成员函数from创建一个LayoutInflater实例(LayoutInflater是一个用来实例化XML布局文件为View对象的类),并且保存在mLayoutInflater中。这样,PhoneWindow类以后就可以通过mLayoutInflater来创建应用程序窗口的视图。PhoneWindow类还有另外一个类型为ViewGroup的成员变量mContentParent,用来描述一个视图容器,这个容器存放的就是成员变量mDecor所描述的视图的内容,不过这个容器也有可能指向的是mDecor本身。
DecorView类是一个FrameLayout的子类,并且是PhoneWindow的内部类,该类就是对普通的FrameLayout进行功能的扩展,比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。它是所有应用窗口的根View ,只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。
在这里插入图片描述
Activity组件会将它所实现的一个Callback接口设置到与它所关联的PhoneWindow对象的父类Window的mCallback中去,这样当这个PhoneWindow对象接收到系统给它分发的IO输入事件,例如,键盘和触摸屏事件,可以转发给与它所关联的Activity组件处理。
在这里插入图片描述
参数mode用来描述窗口的软键盘输入区域的显示模式,它们的含义如下所示:

  1. SOFT_INPUT_STATE_UNSPECIFIED:没有指定软键盘输入区域的显示状态。
  2. SOFT_INPUT_STATE_UNCHANGED:不要改变软键盘输入区域的显示状态。
  3. SOFT_INPUT_STATE_HIDDEN:在合适的时候隐藏软键盘输入区域,例如,当用户导航到当前窗口时。
    1. SOFT_INPUT_STATE_ALWAYS_HIDDEN:当窗口获得焦点时,总是隐藏软键盘输入区域。
  4. SOFT_INPUT_STATE_VISIBLE:在合适的时候显示软键盘输入区域,例如,当用户导航到当前窗口时。
  5. SOFT_INPUT_STATE_ALWAYS_VISIBLE:当窗口获得焦点时,总是显示软键盘输入区域。
    设置完成窗口的软键盘输入区域的显示模式之后,如果Window类的mCallback指向了一个窗口回调接口,那么Window类的成员函数setSoftInputMode还会调用它的成员函数onWindowAttributesChanged来通知与窗口所关联的Activity组件,它的窗口布局属性发生了变化。
    在这里插入图片描述
    参数appToken用来描述当前正在处理的窗口是与哪一个Activity组件关联的,它是一个Binder代理对象,引用了在ActivityManagerService侧所创建的一个类型为ActivityRecord的Binder本地对象。每一个启动起来了的Activity组件在ActivityManagerService侧,都有一个对应的ActivityRecord对象,用来描述该Activity组件的运行状态。这个Binder代理对象会被保存在Window类的成员变量mAppToken中,这样当前正在处理的窗口就可以知道与它所关联的Activity组件是什么。
    参数appName用来描述当前正在处理的窗口所关联的Activity组件的名称,这个名称会被保存在Window类的成员变量mAppName中。
    参数wm用来描述一个窗口管理器。函数接着再使用它来创建一个本地窗口管理器,即一个WindowManagerImpl对象,用来维护当前正在处理的应用程序窗口。
    在这里插入图片描述
    在这里插入图片描述
    PhoneWindow类的成员变量mContentParent用来描述一个类型为ViewGroup的视图对象,或者一个子视图对象,用作UI容器。当它的值等于null时,说明正在处理的应用程序窗口的视图对象还没有创建,就会调用installDecor来创建应用程序窗口视图对象。否则就重新设置应用程序窗口的视图。接着再调用LayoutInflater的inflate方法来将参数layoutResID所描述的UI布局设置到前面所创建的应用程序窗口视图中去。最后会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件,它的视图内容发生改变了。
    在这里插入图片描述
    installDecor会调用generateLayout根据当前应用程序窗口的Feature加载对应的窗口布局文件。这个布局控件必须从ViewGroup类继承下来,用来作为窗口的UI容器。接着会检查前面加载的窗口布局文件是否包含有一个id值为“title”的TextView控件,如果包含有的话,就会用来描述当前应用程序窗口的标题栏。如果当前应用程序窗口是没有标题栏的,即它的Feature位FEATURE_NO_TITLE的值等于1,那么就需要将前面得到的标题栏隐藏起来。注意,mTitleView所描述的标题栏有可能是包含在一个id值为“title_container”的容器里面的,这种情况下,就需要隐藏该标题栏容器。应用程序窗口的标题栏文字保存在mTitle中,可以调用PhoneWindow类的成员函数setTitle来设置。
    在这里插入图片描述
    handleResumeActivity首先调用performResumeActivity来通知Activity组件要被激活了,即导致Activity组件的成员函数onResume被调用。performResumeActivity的返回值是一个ActivityClientRecord对象,该对象的成员变量activity描述的就是正在激活的Activity组件。接下来会判断正在激活的Activity组件是否是可见的。mStartedActivity用来描述一个Activity组件是否正在启动一个新的Activity组件,并且等待这个新的Activity组件的执行结果。如果是true的话,表示在新的Activity组件的执行结果返回来之前,当前Activity组件要保持不可见的状态。然后会调用willActivityBeVisible来检查位于Activity组件a上面的其它Activity组件(包含了Activity组件a正在等待其执行结果的Activity组件)是否是全屏的。如果不是,willActivityBeVisible的返回值就等于true,表示接下来需要显示Activity组件a。
    ActivityClientRecord对象r的成员变量window用来描述当前正在激活的Activity组件a所关联的应用程序窗口对象。当它的值等于null时,并且这个正在激活的Activity组件a还活着,且接下来是可见的,那么就说明要为与Activity组件a所关联的应用程序窗口视图对象关联一个ViewRootImpl对象。
    通过getWindow来获得Activity所关联的应用程序窗口对象。一旦获得了这个应用程序窗口对象(类型为PhoneWindow)之后,就可以调用getDecorView来获得它内部的视图对象。
    在关联应用程序窗口视图对象和ViewRootImpl对象时,还需要第三个参数,即应用程序窗口的布局参数WindowManager.LayoutParams,可以通过调用应用程序窗口的getAttributes来获得。
    最后,还要判断当前正在激活的Activity组件a在本地进程中是否是可见的,即它的成员变量mVisibleFromClient的值是否等于true。如果是可见的,就可以调用本地窗口管理器wm的addView方法来执行关联应用程序窗口视图对象和ViewRootImpl对象的操作。
    在分析addView前,首先了解一下WindowManagerGlobal类是如何关联一个应用程序的View、ViewRoot和WindowManager.LayoutParams对象的。WindowManagerGlobal类有三个变量mViews、mRoots和mParams,它们分别是类型为View、ViewRoot和WindowManager.LayoutParams的数组,它们的长度始终保持相等的。有关联关系的View、ViewRoot和WindowManager.LayoutParams就会分别保存在数组mViews、mRoots和mParams的相同位置上,即mViews[i]、mRoots[i]和mParams[i]所描述的View、ViewRoot和WindowManager.LayoutParams是具有关联关系的。
    在这里插入图片描述
    在这里插入图片描述
    addView首先调用findViewLocked来检查参数view所描述的View对象是否已经存在于数组mViews中。如果已经存在的话,那么就说明该View对象已经关联过ViewRoot和WindowManager.LayoutParams对象了。如果没有被关联过,addView就会创建一个ViewRootImpl对象,并且将它与view和params分别保存在数组mViews、mRoots和mParams的相同位置上。如果数组mViews、mRoots和mParams尚未创建,那么成员函数addView就会首先分别为它们创建一个大小为1的数组,以便可以用来分别保存所要关联的View对象、ViewRoot对象和WindowManager.LayoutParams对象。另一方面,如果数组mViews、mRoots和mParams已经创建,那么成员函数addView就需要分别将它们的大小增加1,以便可以在它们的末尾位置上分别保存所要关联的View对象、ViewRoot对象和WindowManager.LayoutParams对象。
    当view描述的是一个子应用程序窗口的视图对象时,即wparams的type值大于等于WindowManager.LayoutParams.FIRST_SUB_WINDOW并且小于等于WindowManager.LayoutParams.LAST_SUB_WINDOW时,那么还需要找到这个子视图对象的父视图对象panelParentView(这是通过遍历数组mRoots来查找的)。首先,wparams的token指向了一个类型为W的Binder本地对象的IBinder接口,用来描述view所描述的一个子应用程序窗口视图对象所属的父应用程序窗口视图对象。其次,每一个ViewRoot对象都通过mWindow来保存一个类型为W的Binder本地对象,因此,如果在数组mRoots中,存在一个ViewRoot对象,它的mWindow所描述的一个W对象的IBinder接口等于wparams的token所描述的IBinder接口时,那么就说明与该ViewRoot对象所关联的View对象为参数view的父应用程序窗口视图对象。
    在这里插入图片描述
    参数view会分别保存在mView以及AttachInfo的mRootView中,而参数attrs所描述的WindowManager.LayoutParams对象会被拷贝到mWindowAttributes中去。
    当参数panelParentView的值不等于null时,就表示参数view描述的是一个子应用程序窗口视图对象。参数panelParentView描述的就是一个父应用程序窗口视图对象。这时就需要获得用来描述这个父应用程序窗口视图对象的一个类型为W的Binder本地对象的IBinder接口,以便可以保存在mAttachInfo的mPanelParentWindowToken中去。这样以后就可以知道mView所描述的一个子应用程序窗口视图所属的父应用程序窗口视图是什么了。注意,通过调用参数panelParentView的所描述的一个View对象的成员函数getApplicationWindowToken即可以获得一个对应的W对象的IBinder接口。
    就可以将mAdded的值设置为true了,表示ViewRootImpl对象已经关联好一个View对象了。接下来,还需要执行以下两个操作:
  6. 调用requestLayout来请求对应用程序窗口视图的UI作第一次布局;
  7. 调用sWindowSession所描述的一个类型为Session的Binder代理对象的addToDisplay方法来请求WindowManagerService增加一个WindowState对象,以便用来描述当前正在处理的ViewRootImpl所关联的应用程序窗口。
  8. Android应用程序窗口的绘图表面是通过两个Surface对象来描述的,一个是在应用程序进程创建的,另一个是在WindowManagerService服务侧创建的,它们对应于SurfaceFlinger服务的同一个Layer对象。
    

10.应用程序进程侧的Surface对象负责绘制应用程序窗口的UI,即往应用程序窗口的图形缓冲区填充UI数据。位于WindowManagerService服务侧的Surface对象负责设置应用程序窗口的属性,如位置、大小等属性。因为绘制应用程序窗口是独立的,由应用程序进程来完即可,而设置应用程序窗口的属性却需要全局考虑,即需要由WindowManagerService服务来统筹安排。
在这里插入图片描述
ViewRootImpl类的成员函数relayoutWindow调用静态成员变量sWindowSession所描述的一个实现了IWindowSession接口的Binder代理对象的成员函数relayout来请求WindowManagerService服务对成员变量mWindow所描述的一个应用程序窗口的UI进行重新布局,同时,还会将成员变量mSurface所描述的一个Surface对象传递给WindowManagerService服务,以便WindowManagerService服务可以根据需要来重新创建一个绘图表面给成员变量mWindow所描述的一个应用程序窗口使用。
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
Rect outFrame, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface)
返回结果包含了一系列与参数window所描述的应用程序窗口相关的参数:

  1. 窗口的大小:最终保存在输出参数outFrame中。
  2. 窗口的内容区域边衬大小:最终保存在输出参数outContentInsets中。
  3. 窗口的可见区域边衬大小:最终保存在输出参数outVisibleInsets中。
  4. 窗口的配置信息:最终保存在输出参数outConfig中。
  5. 窗口的绘图表面:最终保存在输出参数outSurface中。
    一个应用程序窗口的绘图表面在创建完成之后,函数就会将得到的一个Surface对象保存在当前正在处理的WindowState对象的成员变量mSurface中。如果创建绘图表面失败,并且从Surface类的构造函数抛出来的异常的类型为Surface.OutOfResourcesException,就说明系统当前的内存不足了,这时候函数就会调用WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来回收内存。
    接下来会设置当前正在处理的WindowState对象所描述应用程序窗口的以下属性:
  6. X轴和Y轴位置。mFrame是用来描述应用程序窗口的位置和大小的,其中,位置就是通过它所描述的一个Rect对象的成员变量left和top来表示的,它们分别应用窗口在X轴和Y轴上的位置。此外,当一个WindowState对象所描述的应用程序窗口是一个壁纸窗口时,该WindowState对象的成员变量mXOffset和mYOffset用来描述壁纸窗口相对当前要显示的窗口在X轴和Y轴上的偏移量。因此,将WindowState类的mXOffset的值加上mFrame的left的值,就可以得到一个应用程序窗口在X轴上的位置。同样,将WindowState类的mYOffset的值加上mFrame的top的值,就可以得到一个应用程序窗口在Y轴上的位置。最终得到的位置值分别保存在WindowState类的mSurfaceX和mSurfaceY,并且会调用setPosition来将它们设置到SurfaceFlinger服务中去。
  7. Z轴位置。WindowState类的成员变量mAnimLayer用来描述一个应用程序窗口的Z轴位置,因此,会先将它保存在WindowState类的mSurfaceLayer中,然后再调用setLayer来将它设置到SurfaceFlinger服务中去。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!