天天看点

源码阅读分析 - Window底层原理与系统架构

做了一段的时间的 android 我们就开始听到有人说 AMS、WMS、Window、WindowManager、WindowManagerService等等这些词汇,可能了解但是脑海里未必有架构图, 这次我们就从源码的角度来了解一下。在阅读本文之前希望你可以花点时间了解下面几篇文章:

1. 插件式换肤框架搭建 - setContentView源码阅读

2. Android进程间的通信 - IPC(机制)Binder的原理和源码阅读

2. Android插件化架构 - Activity的启动流程分析

3. 源码解析 - View的绘制流程

上面几篇文章和我今天的这篇文章有着千丝万缕的联系,我为什么可以把他们分开来?有很重要的一点,我们是需要用到才会去看源码,打个比方我想做一个皮肤切换的功能,那么我肯定需要去了解布局资源的加载过程,而这个你仅仅从网上看看别人写好的文章或者 copy 几行代码相信你应该很难做到,当然去 github 上面选个 demo 用用也行,但你心里不觉得少了点什么吗?还有我们最好能够点到即止,带着疑问,只需要清楚我们想要了解什么,今天的很多源码或多或少都会涉及到上面几篇文章的知识点但我们不用管。相信我,只要你能够多对着别人的文章自己打开源码看看,随着时间的推移当我们再看系统源码的时候就会得心应手,解决问题再也不是靠蒙和试(如果你在开发过程中有时候靠蒙可以在文章末尾刷个赞),而且如果心中有整个 android 应用层的源码架构图,对于开发和解决问题方面屡试不爽(如果你能看懂 native 层的源码更好)。

我们只是知道 setContentView 方法可以设置显示我们的布局,这篇 插件式换肤框架搭建 - setContentView源码阅读 文章只是带大家了解了布局的层次结构,但你了解 Window 和 WindowManager 都干了些什么吗?又或者当我们触摸一个 EditText 的时候会弹出一个系统的键盘,为什么弹出键盘我们的 Activity 布局会自动做调整?我们弹一个 Toast 但就算我们退出 Activity 或是整个应用 Toast 还是会存在?

我们以 Activity 的 setContentView 作为入口可以看到这么两行代码:

getWindow().setContentView(layoutResID) 这行代码只是去解析我们的布局,没干其他任何事情,这行代码我就不再分析源码了,今天的重点不在这里如果想了解请看这篇插件式换肤框架搭建 - setContentView源码阅读,getWindow() 返回的是 mWindow 而 mWindow 是在 attach 方法中实例化的:

那么 attach 方法到底是在什么时候调用的呢?在 ActivityThread 中的 performLaunchActivity 方法中调用的,这里涉及到 Activity 的启动流程分析想了解请看这篇Android插件化架构 - Activity的启动流程分析

目前我们只知道了通过 setContentView 方法会调用 mWindow 的 setContentView 方法,这个方法只是去解析我们的布局而已什么时都没做,而 mWindow 的实例实在 activity 的 attach 方法中调用的,而这个方法是由 ActivityThread 调用的然后就没有了,那么布局到底是怎么显示的?

在Android插件化架构 - Activity的启动流程分析中我们能够在 ActivityThread 中找到 handleResumeActivity 这个方法:

最终调用了 ViewManager 的 addView 方法,但是我们发现 ViewManager 其实是个接口,所以我们得去找实现方法,是在上面 Activity 的 attach 方法通过 context.getSystemService(Context.WINDOW_SERVICE) 获取的,找到 context 的实例类 ContextImpl 的 getSystemService 方法其实调用了 SystemServiceRegistry.getSystemService 方法:

SYSTEM_SERVICE_FETCHERS 是一个静态的 HashMap 对象,是通过静态代码块赋值的。这里其实我们可以总结一下,通过 Context 获取的系统服务其实早就被注册和添加到 SYSTEM_SERVICE_FETCHERS 集合中了,这是典型的单例设计模式。至此我们总算找到了这个类 WindowManagerImpl 的 addView 方法。

WindowManagerImpl 是 ViewManager 的实现类却把活交给了 WindowManagerGlobal 方法,而且我们发现 mGlobal 对象尽然是个单例,为什么要这么设计在文章中我就不做过多的讲解了。我们看下 mGlobal 的 addView 方法:

走了这么久最重要的方法其实就是 root.setView(view, wparams, panelParentView); 这行代码,很复杂偏偏这个方法又是最主要的,希望我讲得并不太深入而且通俗易懂。在分析这个之前我们先讲一下上面反复出现的 type 属性。

Window(窗口)是有类型的,而且上面我们也看到了不同的 type 会做不同的处理,Window 分为三种类型:系统 Window,应用程序 Window,子 Window

常见的系统Window,比如在手机电量低的时候,会有一个提示电量低的Window,我们输入文字的时候,会弹出输入法Window,还有搜索条Window,来电显示Window,Toast对应的Window,可以总结出来,系统Window是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统Window,因为没有权限,这个权限只有系统进程有。所对应的层级区间是 2000 以上

应用程序Window,比如 Activity 就是一个应用程序 Window 从上面源码可以看到 type 给的是 TYPE_BASE_APPLICATION 。所对应的层级区间是 1 - 99

子Window,所谓的子Window,是说这个Window必须要有一个父窗体,比如PopWindow,Dialog 等等 。所对应的层级区间是 1000 - 1999

那么每个层级具体有那些,请看 WindowManager.LayoutParams中的 type 值,这里我贴出来但是不做翻译,因为我英语不好:

回到 ViewRootImpl 中的 setView 方法:

requestLayout() 这个方法很有含金量,如果想了解看下这篇 源码解析 - View的绘制流程 ,我们主要还是分析mWindowSession.addToDisplay 方法,mWindowSession 又是什么呢?看到 Session 其实还是知道什么意思,是不是真的和网络的 Session 差不多呢?mWindowSession 是通过 WindowManagerGlobal.getWindowSession(); 获取的,我们去看下:

这又是一个典型的 IPC 的通信机制,和 AMS 非常的类似想了解看下这篇 Android进程间的通信 - IPC(机制)Binder的原理和源码阅读 ,现在我们去服务端 WindowManagerService 的 openSession 方法:

Session我们总算是获取到了,服务端 WindowManagerService 创建了一个 Session 对象返回回来了,接下来我们发现 Session 的 addToDisplay 方法来到了 WindowManager 的 addWindow 方法,相信应该差不多快完了吧,这不是人干的事:

这段代码首先在WindowManagerService类的成员变量mWindowMap所描述的一个HashMap中检查是否存在一个与参数client所对应的WindowState对象,如果已经存在,那么就说明WindowManagerService服务已经为它创建过一个WindowState对象了,因此,这里就不会继续往前执行,而是直接返回一个错误码WindowManagerImpl.ADD_DUPLICATE_ADD。我们继续往前看代码:

参数attrs指向的是一个WindowManager.LayoutParams对象,用来描述正在启动的Activity组件的UI布局,当它的成员变量type的值大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW的时候,就说明现在要增加的是一个子窗口。在这种情况下,就必须要指定一个父窗口,而这个父窗口是通过数attrs指向的是一个WindowManager.LayoutParams对象的成员变量token来指定的,因此,这段代码就会调用WindowManagerService类的另外一个成员函数windowForClientLocked来获得用来描述这个父窗口的一个WindowState对象,并且保存在变量attachedWindow。

如果得到变量attachedWindow的值等于null,那么就说明父窗口不存在,这是不允许的,因此,函数就不会继续向前执行,而是直接返回一个错误码WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。另一方面,如果变量attachedWindow的值不等于null,但是它的成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量type的值也是大于等于FIRST_SUB_WINDOW并且小于等于LAST_SUB_WINDOW,那么也说明找到的父窗口也是一个子窗口,这种情况也是不允许的,因此,函数就不会继续向前执行,而是直接返回一个错误码WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN。

继续看代码:

通过上面的合法性检查之后,这里就可以为正在增加的窗口创建一个WindowState对象了。 WindowManagerService类的成员变量mPolicy指向的是一个实现了WindowManagerPolicy接口的窗口管理策略器。在Phone平台中,这个窗口管理策略器是由com.android.internal.policy.impl.PhoneWindowManager来实现的,它负责对系统中的窗口实现制定一些规则。这里主要是调用窗口管理策略器的成员函数adjustWindowParamsLw来调整当前正在增加的窗口的布局参数,以及调用成员函数prepareAddWindowLw来检查当前应用程序进程请求增加的窗口是否是合法的。如果不是合法的,即变量res的值不等于WindowManagerImpl.ADD_OKAY,那么函数就不会继续向前执行,而直接返回错误码res。

我们就先看到这里了,你甚至还可以在去了解 WindowState 的成员变量都有一些啥作用,又或者是那些合法的检查代码都有什么用等等,最后我再画一张草图:

源码阅读分析 - Window底层原理与系统架构

我花了大概半个多月的时间才勉强看懂一些源码,当然包括这篇文章中的所有源码链接,如果看不太懂需要多花些时间,如果文字让你受不了可以看看我的直播视频,同时这也是自定义View部分的最后一篇文章了。

所有分享大纲:Android进阶之旅 - 自定义View篇

视频讲解地址:http://pan.baidu.com/s/1pLNXkxl