一篇通俗易懂的 Android 视图系统设计与实现,精通 android 网络开发 pdf
1.1 五彩斑斓的效果皆源自 Canvas
Android 手机本质是一块屏幕,为了方便开发者绘制出五彩斑斓的效果,Android 系统在Java Framework
封装了一块画布Canvas
,它配合Paint
、Matrix
几乎可以画出任意效果 但光有Canvas
还远远不够,因为它上手难度高、复用率低,绘制各种复杂界面几乎成了不可完成的任务。面对这种痛点 Android 系统通过模板设计模式
封装了一个用来管理绘制的组件View
,屏蔽大量细节的同时提供三个模板方法measure、layout、draw
,开发者可以通过View
的三大模板方法自行定义视图的宽高、位置、形状
,解决了大量模板代码以及复用率低的问题。
一个复杂的界面通常会包含很多元素比如文字、图片等
,根据单一设计原则
Android 将其封装为TextView、ImageView
。看起来万事大吉,但摆放这些View
的时候又是一个大工程,各种坐标计算不 一会就晕头转向的,实际上摆放规则无非就那几种,所以Android
利用View
的layout
特性封装了RelativeLayout、LinearLayout
等layout
用来控制各View
之间的位置关系,进一步提升开发者效率。
所以 View 的出现是为了解决Canvas
使用难度高、复用率低的问题。仅就Java Framework
来讲:“Canvas 可以没有 View,但 View 不能没有 Canvas。
”,归根到底View
只是视图排版工具。而ViewGroup
则是View
的排版工具
引号内容摘自 《重学安卓:是让人 过目难忘 的 Android GUI 族谱解析啊!》
1.2 如何管理错综复杂的 View?
通过自定义 View 可以绘制出我们任意想要的效果,一切看似很美好。正当你盯着屏幕欣赏自己的作品时,“啪”糊上来一个其他界面,一通分析得知,原来其他app
也通过View
操控了屏幕,你也不甘示弱通过相同操作重新竞争到屏幕,如此反复进行 不可开交时屏幕黑了,得,还是换回塞班系统吧~~~
玩笑归玩笑,回归到问题本身。由于对View
的管理不当造成了屏幕很混乱的情况。按常理来讲当用户在操作一个 app 时肯定不希望其他 app 蹦出来,所以在此背景下急需一套机制
来管理错综复杂的View
。于是 Android 在系统进程中创建了一个系统服务WindowManagerService(WMS)
专门用来管理屏幕上的窗口
,而View
只能显示在对应的窗口上,如果不符合规定就不开辟窗口
进而对应的View
也无法显示
为什么WMS需要运行在系统进程?
由于每个
app
都对应一个进程,想要管理所有的应用进程,WMS
需要找一个合适的地方能凌驾于所有应用进程之上,系统进程是最合适的选择
1.3 不可缺少的窗口生命周期
自定义View
可以定制各种视图效果,窗口
可以让View
有条不紊的显示,一切又美好了起来。但问题又来了,每个App
都会有很多个界面(窗口)
,仅靠窗口/View
来控制窗口和视图会面临如下问题:
初始化时机不明确
无法感知
前景/背景
切换不能及时销毁
等等…
以上一系列问题都是因为窗口
没有一套完善的生命周期导致的,如果将生命周期
强行加到窗口
上便违背了单一设计原则
。于是 Android 基于模板设计模式
设计出了Activity
并基于迪米特法则
将窗口
的管理屏蔽在内部,并暴露出对应的模版方法(onCreate、onStart、onResume…),让开发者只专注于视图排版(View)
和生命周期
,无需关心窗口
的存在
所以,单纯说通过Activity
创建一个界面似乎又不那么准确,一切窗口
均源自于WMS
,而窗口
中内容由View
进行填充,Activity
只是在内部"间接"
通过WMS
管理窗口并协调好窗口
与View
的关系,最后再赋予生命周期
等 功能而已。
关于Activity
如何管理窗口/View
? 请看第二小节
读源码的目的是为了理清设计流程,千万不要因果倒置陷入到代码细节当中,所以要懂得挑重点,讲究点到为止。本文为了提供更好的阅读体验,会将源码中大部分无用信息删掉,只保留精华。
2.1 Activity 的由来
Activity
从何而来?想追溯到源头,恐怕要到从开天辟地时造就第一个受精卵开始
开天辟地的 Zygote 从何而来
Android 系统会在开机时由Native
层创建第一个进程init进程
,随后init进程
会解析一个叫init.rc
的本地文件创建出Zygote
进程
字如其名,Zygote
的职责就是孵化进程。当孵化出的第一个进程SystemServer进程
后退居幕后,通过Socket
静等创建进程
的呼唤,一切应用进程均由Zygote
进程孵化
SystemServer 进程的职责
SystemServer
是Zygote
自动创建的进程,并且会长时间驻留在内存中,该进程内部会注册各种Service
如:
ActivityManagerService(AMS):用来
创建应用进程(通过socket ipc通知zygote进程)
、管理四大组件
WindowManagerService(WMS):用来开辟和管理屏幕上的
窗口
,让视图有条不紊的显示
InputManagerService(IMS):用来处理和分发各种
事件
等等…
为什么要将这些系统服务放在单独进程?
像`AMS、W
MS、IMS都是用来处理一些系统级别的任务,比如
Activity存在
任务栈/返回栈的概念,如果在通过
Activity进行
应用间跳转时,需要协调好
任务栈/返回栈的关系,而不同应用又属于不同进程,所以需要一个地方能凌驾于所有应用进程之上,而单独进程是最好的选择。关于
WMS、IMS 等其他 Service 同理`,就不再赘述
应用进程的创建过程
前面说到AMS
可以通知Zygote进程
孵化应用进程,那究竟何时通知
呢?其实大家应该已经猜到了,通过点击桌面上应用图标可以开启一个应用,所以AMS
就是在此时通知Zygote
创建应用进程。但桌面
又是什么东西它从何而来?其实桌面也是一个Activity
,它由AMS
自动创建
回归正题,点击应用图标到 Activity 的启动 这之间经历了什么流程?下面我简单列一下:
当点击一个 App 图标时,如果对应的应用进程还没有创建则会通过
Binder IPC
通知到AMS
创建应用进程应用进程启动后会执行我们所熟悉的
main方法
,而这个main方法
则位于ActivityThread
这个类中,main方法
对应的就是 Android主线程
ActivityThread
的main方法
首先会调用Looper.loop()
,用来循环处理主线程Hanlder
分发的消息。接下来的
main方法
会发送一个BIND_APPLICATION
的消息,Looper
收到后会通过Binder IPC
通知AMS
创建App进程
对应的Application
Application
创建后会再次通过Binder IPC
通知AMS
要创建Activity
,AMS
验证后会回到App进程
,回到
App进程
后会间接调用ActivityThread#performLaunchActivity()
来真正启动创建Activity
,并且执行attach()
和onCreate()
。
tips
Application
和Activity
并不是通过AMS
直接创建的,AMS
只是负责管理和验证,真正创建具体对象还得到 App 进程
Android 视图系统是一个很庞大的概念,几乎贯穿了整个Java Framework
,由于作者能力
以及篇幅
的原因,无法一文将Java Framework
讲解清楚。所以就描述式的说了下系统进程、应用进程以及 Activity 的由来,尽可能你更清晰的认识 Android 视图系统。
2.2 PhoneWindow 不等价于"Window(窗口)"
我之所以第一小节没有将
窗口
描述成Window
是怕大家将二者混淆,因为应用进程的Window/PhoneWindow
和真正的窗口
根本就是两个概念,作者也曾在阅读源码时就这个问题困惑了很久。在此非常感谢一只修仙的猿
在 Android全面解析之Window机制 一文中给了我答案
Android SDK 中的Window
是一个抽象类,它有一个唯一实现类PhoneWindow
,PhoneWindow
内部会持有一个DecorView(根View)
,它的职责就是对DecorView
做一些标准化的处理,比如标题、背景、导航栏、事件中转等,很显然与我们前面所说的窗口
概念不符合
那PhoneWindow
何时被创建?
2.1
小结我提到可以通过ActivityThread#performLaunchActivity()
创建Activity
,来看下其代码:
#ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
//注释 1
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
if (activity != null) {
...
//注释 2.
activity.attach(...);
...
//注释 3.
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
...
return activity;
}
首先通过注释1
处创建一个Activity
对象,然后在注释2
处执行其attach(..)
方法,最后在通过callActivityOnCreate()
执行Activity
的onCreate()
方法
先来看attach
做了什么事情:
#Activity
final void attach(...){
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
mWindow.setWindowManager(...);
mWindowManager = mWindow.getWindowManager();
...
}
Activity
会在attach()
方法中创建一个PhoneWindow
对象并复制给成员变量mWindow
,随后执行WindowManager
的setter、getter
。来重点看一下setter
方法:
#Window
public void setWindowManager(...) {
...
if (wm == null) {
//注释 1
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//注释 2
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
注释1
处会通过系统服务获取一个WindowManager
类型对象,用来管理Window
。
注释2
会通过WindowManager
创建一个WindowManagerImpl
对象,实际上WindowManager
是一个接口,它继承自ViewManager
接口,而WindowManagerImpl
是它的一个实现类
绕来绕去原来是通过WindowManager
创建了另一个WindowManager
,看起来多此一举,那Android
为什么要这样设计呢?
首先
WindowManager
具备两个职责,管理Window
和创建WindowManager
。系统服务获取的WindowManager
具备创建Window
功能,但此时并未与任何Window
关联。而通过createLocalWindowManager
创建的WindowManager
会与对应的Window
一对一绑定。所以前者用于创建WindowManager
,后者用于与Window
一对一绑定,二者职责明确,但让作者费解的是为什么不基于单一设计原则
把创建
过程抽取至另一个类?如果有知道的同学可以评论区留言,事先谢过~
关于WindowManagerImpl
如何管理Window
先暂且不提,下面文章会说到
PhoneWindow
已经创建完毕,但还没有跟Activity/View
做任何关联。扒一扒PhoneWindow
的源码你会发现,它内部只是设置了标题、背景
以及事件
的中转等工作,与窗口
完全不搭嘎,所以切勿将二者混淆
2.3 DecorView 的创建时机
通过2.2
可知 Activity
的attach()
运行完毕后会执行onCreate()
,通常我们需要在onCreate()
中执行stContentView()
才能显示的XML Layout
。关于stContentView()
顾名思义就是设置我们的Content View
嘛,内部代码如下:
#Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
...
}
public Window getWindow() {
return mWindow;
}
首先通过getWindow()
获取到attach()
阶段创建的PhoneWindow
,随后将layoutResID(XML Layout)
传递进去,继续跟:
#PhoneWindow
ViewGroup mContentParent;
public void setContentView(int layoutResID) {
//注释 1
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//注释 2
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
注释1
处会判断mContentParent
是否为空,如果为空会通过installDecor()
对其实例化,否则移除所有子 View。
注释2
处会将layoutResID
对应的XML
加载到mContentParent
。到此为止唯一的疑问是mContentParent
如何被创建的,跟一下installDecor()
:
#PhoneWindow
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
首先创建DecorView
类型对象并赋值给引用mDecor
。那什么是DecorView
?
DecorView
继承自FrameLayout
,内部有一个垂直布局的LinearLayout
用来摆放状态栏、TitleBar、ContentView、导航栏
,其中ContentView
就是用来存放由Activity#setContentView
传入的Layout
。之所以设计出DecorView
是因为状态栏、导航栏等
需要做到系统统一,并将其管控操作屏蔽在内部,只暴露出ContentView
由开发者填充,符合迪米特法则
再回到mDecor
的创建过程,跟一下generateDecor(-1)
代码:
#PhoneWindow
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
直接new
出来了一个DecorView
。再回到我们最初的疑问,mContentParent
从何而来?installDecor()
创建出DecorView
会通过generateLayout(mDecor)
创建mContentParent
。generateLayout(mDecor)
代码很长就不贴了,内部会通过mDecor
获取到mContentParent
并为其设置主题、背景等
。
到此阶段DecorView
创建完毕并与XML Layout
建立了关联,但此时根View(DecorView)
还未与窗口建立关联,所以是看不到的。
为什么要在onCreate执行setContentView?
通过
setContentView
可以创建DecorView
,而一个Activity
通常只有一个DecorView(撇去Dialog等)
,如若将setContentView
放在start、resume
可能会创建多个DecorView
,进而会造成浪费。所以onCreate
是创建DecorView
的最佳时机
评论