写点什么

一篇通俗易懂的 Android 视图系统设计与实现,精通 android 网络开发 pdf

用户头像
Android架构
关注
发布于: 刚刚

1.1 五彩斑斓的效果皆源自 Canvas

Android 手机本质是一块屏幕,为了方便开发者绘制出五彩斑斓的效果,Android 系统在Java Framework封装了一块画布Canvas,它配合PaintMatrix几乎可以画出任意效果 但光有Canvas还远远不够,因为它上手难度高、复用率低,绘制各种复杂界面几乎成了不可完成的任务。面对这种痛点 Android 系统通过模板设计模式封装了一个用来管理绘制的组件View,屏蔽大量细节的同时提供三个模板方法measure、layout、draw,开发者可以通过View的三大模板方法自行定义视图的宽高、位置、形状,解决了大量模板代码以及复用率低的问题。


一个复杂的界面通常会包含很多元素比如文字、图片等,根据单一设计原则Android 将其封装为TextView、ImageView。看起来万事大吉,但摆放这些View的时候又是一个大工程,各种坐标计算不 一会就晕头转向的,实际上摆放规则无非就那几种,所以Android利用Viewlayout特性封装了RelativeLayout、LinearLayoutlayout用来控制各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. 实现流程




读源码的目的是为了理清设计流程,千万不要因果倒置陷入到代码细节当中,所以要懂得挑重点,讲究点到为止。本文为了提供更好的阅读体验,会将源码中大部分无用信息删掉,只保留精华。

2.1 Activity 的由来

Activity从何而来?想追溯到源头,恐怕要到从开天辟地时造就第一个受精卵开始


开天辟地的 Zygote 从何而来


Android 系统会在开机时由Native层创建第一个进程init进程,随后init进程会解析一个叫init.rc的本地文件创建出Zygote进程


字如其名,Zygote的职责就是孵化进程。当孵化出的第一个进程SystemServer进程后退居幕后,通过Socket静等创建进程的呼唤,一切应用进程均由Zygote进程孵化


SystemServer 进程的职责


SystemServerZygote自动创建的进程,并且会长时间驻留在内存中,该进程内部会注册各种Service 如:


  • ActivityManagerService(AMS):用来创建应用进程(通过socket ipc通知zygote进程)、管理四大组件


  • WindowManagerService(WMS):用来开辟和管理屏幕上的窗口,让视图有条不紊的显示


  • InputManagerService(IMS):用来处理和分发各种事件


  • 等等…


为什么要将这些系统服务放在单独进程?


像`AMS、W


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


MS、IMS都是用来处理一些系统级别的任务,比如Activity存在任务栈/返回栈的概念,如果在通过Activity进行应用间跳转时,需要协调好任务栈/返回栈的关系,而不同应用又属于不同进程,所以需要一个地方能凌驾于所有应用进程之上,而单独进程是最好的选择。关于WMS、IMS 等其他 Service 同理`,就不再赘述


应用进程的创建过程


前面说到AMS可以通知Zygote进程孵化应用进程,那究竟何时通知呢?其实大家应该已经猜到了,通过点击桌面上应用图标可以开启一个应用,所以AMS就是在此时通知Zygote创建应用进程。但桌面又是什么东西它从何而来?其实桌面也是一个Activity,它由AMS自动创建


回归正题,点击应用图标到 Activity 的启动 这之间经历了什么流程?下面我简单列一下:


  • 当点击一个 App 图标时,如果对应的应用进程还没有创建则会通过Binder IPC通知到AMS创建应用进程

  • 应用进程启动后会执行我们所熟悉的main方法,而这个main方法则位于ActivityThread这个类中,main方法对应的就是 Android主线程

  • ActivityThreadmain方法首先会调用Looper.loop(),用来循环处理主线程Hanlder分发的消息。

  • 接下来的main方法会发送一个BIND_APPLICATION的消息,Looper收到后会通过Binder IPC通知AMS创建App进程对应的Application

  • Application创建后会再次通过Binder IPC通知AMS要创建ActivityAMS验证后会回到App进程

  • 回到App进程后会间接调用ActivityThread#performLaunchActivity()来真正启动创建Activity,并且执行attach()onCreate()


tips


ApplicationActivity并不是通过AMS直接创建的,AMS只是负责管理和验证,真正创建具体对象还得到 App 进程


Android 视图系统是一个很庞大的概念,几乎贯穿了整个Java Framework,由于作者能力以及篇幅的原因,无法一文将Java Framework讲解清楚。所以就描述式的说了下系统进程、应用进程以及 Activity 的由来,尽可能你更清晰的认识 Android 视图系统。

2.2 PhoneWindow 不等价于"Window(窗口)"

我之所以第一小节没有将窗口描述成Window是怕大家将二者混淆,因为应用进程的Window/PhoneWindow和真正的窗口根本就是两个概念,作者也曾在阅读源码时就这个问题困惑了很久。在此非常感谢一只修仙的猿Android全面解析之Window机制 一文中给了我答案


Android SDK 中的Window是一个抽象类,它有一个唯一实现类PhoneWindowPhoneWindow内部会持有一个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()执行ActivityonCreate()方法


先来看attach做了什么事情:


#Activity


final void attach(...){


...


mWindow = new PhoneWindow(this, window, activityConfigCallback);


...


mWindow.setWindowManager(...);


mWindowManager = mWindow.getWindowManager();


...


}


Activity会在attach()方法中创建一个PhoneWindow对象并复制给成员变量mWindow,随后执行WindowManagersetter、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可知 Activityattach()运行完毕后会执行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)创建mContentParentgenerateLayout(mDecor)代码很长就不贴了,内部会通过mDecor获取到mContentParent并为其设置主题、背景等


到此阶段DecorView创建完毕并与XML Layout建立了关联,但此时根View(DecorView)还未与窗口建立关联,所以是看不到的。


为什么要在onCreate执行setContentView?


通过setContentView可以创建DecorView,而一个Activity通常只有一个DecorView(撇去Dialog等),如若将setContentView放在start、resume可能会创建多个DecorView,进而会造成浪费。所以onCreate是创建DecorView的最佳时机

2.4 ViewRootImpl 如何协调 View 和 Window 的关系?

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
一篇通俗易懂的Android视图系统设计与实现,精通android网络开发pdf