过目难忘 Android GUI 关系梳理

本文为《重学安卓》专栏《过目难忘 Android GUI 关系梳理》篇的试读内容,

我们截取了原文前半部分作为试读内容,您可以免费阅读和 “全文转载” 本文。

前言

从事 Android 开发 3~5 年,从 “纯业务开发” 进阶到 “细节定制工作”,难免高频接触 “自定义 View”、“GUI 性能优化” 等领域的概念和问题,

诸如 Canvas、Paint、Path、View、Drawable、Layout、Inflater、include、merge、ViewStub、PhoneWindow、ViewRootImpl …

网上介绍这些概念的文章数不胜数,甚至还有像上一期《百闻不如一见的 视图系统 架构全貌》 咱们在文末推荐的 “图文并茂” 自定义 View 教程,

那为什么还是让无数 刚刚想要进阶的小伙伴 感到困扰呢?

Android GUI 系统仍在困扰着 80% 的进阶者

因素有很多,除了当事人 缺乏 “从 0 到 1 完整踩坑” 的经历 外,我们在互联网上所接触的内容也是一大原因 —— 多数网文 只关心 “是什么、怎么做”,却对 “为什么” 绝口不提

这使得读者们首先就 很难有机会搞明白:为什么要阅读这些文章、为什么要学习这些技术点、每个技术的 “作用边界” 到底是从哪到哪、技术之间的 “关系和顺序” 又该如何串起来 … 乃至网上的教程 再好再多,也无从下手、无从看起。

与此同时,过往的文章中 我们多次提到,凡事唯有 首先通过 “深度思考” 为自己打造一把 “知根知底” 的钥匙,才有机会打开 新世界的大门,从而借助前人已铺设好的 "是什么、怎么做",来玩转那个领域的技术,

因而这一期,我们继续以 “深度思考” 的方式,来 “有层次感” 地铺垫关于 Android GUI 系统的 感性认知

考虑到这样 精心打磨的摆渡文 是看一篇少一篇,因而 就算不去 hold 住面试官,也请务必跟随本文的脚步,将 Android GUI 系统的来龙去脉 无痛地过一遍

文章目录一览

是一分为二的理解方式

阅读过 上一期 的小伙伴已确知:

视图系统主要包括 排版 渲染事件 分发 这两大类工作。

其中 客户端 运行在 “用户进程” 中,负责 可视化内容的排版 和 向服务端输出,以及 接收来自服务端的事件 并在视图树中分发

视图服务 则运行在 “系统进程” 中,负责 对排版内容的渲染,以及 传递触控事件给客户端

考虑到 日常工作 和 进阶的困扰 主要集中在 客户端 排版 API 这一块,因而本文主要分为两个部分来讲解:

第一部分主要是 单刀直入 “推理和解析” 排版的根基之所在,以及 层层递进的 “客户端 排版 API” 关系网

第二部分 我们再以 “特定视角” 来解析整个 Android GUI 系统。

谁才是可视化排版的根基?

早在《重学安卓:Activity 的快乐你不懂》一文中,我们就对 Android 的可视化系统 做了一次简要的推理,

其实光是截取 “这篇文章的中间部分” 加以放大,就能 “单刀直入” 解决 80% 小伙伴 关于排版 的困扰 ——

我们不如从这篇文章的 “Window” 一节开始深入:

“Window” 的存在,主要是为了管理 UI 内容的排版。

那么有没有读者 细思过这句话 —— 它作为管理者,其背后,究竟是 哪位 “绘制者” 在被管理呢

"那时候" 并没有所谓的 View …… 那么究竟是谁在绘制?…

一路追根溯源,噢!原来是 Canvas ——

Canvas 才是可视化排版的根基!排版依赖于绘制 —— 在排版这件事上,Canvas 可以没有 View,但 View 不能没有 Canvas。

Paint 和 Path 都是 Canvas 的小弟,真正 与排版输出 存在直接关系的 最源头 API,就是 Canvas 本尊。

View 和 Drawable 不过是排版的模板

然而 既然是视图系统,那就不仅仅是原始、无序的 “绘制” 本身。

因而在有了 “Window” 和 Canvas 的基础上,还需确立 View / ViewGroup 这样规则的 排版标准,从而我们得以 在这套 可拓展的标准 的基础上,构建 可复用的具体模板

例如 Button、TextView、ImageView,这些,都是 "可复用的模板",开发者日常只需与 上层的这些 View 打交道,而无需直接与 Canvas 打交道、无需做什么都得先手动基于 Canvas 来写个几百行的排版代码。

并且,如果对 现成的模板感到不满意,也可以自己再封装一个 新的模板,也即人们通常所说的 "自定义控件"。

与此同时,View 只是大致描述了 视图构建的模板,如果要更改 具体 View 的可视化细节,那岂不是又要接触 Canvas?

因此,同样是出于 灵活性和复用 的考虑,而衍生出 Drawable 的设计,它的存在是用于负责更具体的 可视化细节的模板:

例如通过 ShapeDrawable 来描述 视图的轮廓和背景、通过 StateListDrawable 来描述 视图的点击效果 等等,

使得我们绝大部分情况下 都能使用成熟的 Drawable 模板 来描述排版细节,避免良莠不齐的开发人员 因直接与 Canvas 打交道 而埋下的 不可预知的隐患。

划重点 👆 👆 👆

图片截自 “小米天气” 客户端:是无处不在的、各种规格的圆角。

Layout 和 Inflater 不过是后来者

所以 Layout 和 LayoutInflater 不过是在有了 View 和 Drawable 的基础上,才有的后来者。

说来你可能不信,可这也许真是 Google 宠 Android 开发者的证明 ~

接触过的 iOS 开发者多有抱怨,在 iOS 上写布局 实在酸爽,没有预览,全靠想象 …

是的,没错,Layout 对应的是 视图树;shape、selector 等 对应的是 Drawable —— Google 模仿微软 WPF 的设计,巧妙地通过 XML 的方式,来实现 一目了然的 声明式编程可实时预览的布局,以及 向 MVC 模式致敬 的 视图 和 视图控制器分离 —— 这些都极大地方便了开发者 创建和修改可视化内容。

然而,XML 声明式编程 在解决上述问题的同时,引入了新的问题:

1.XML 排版资源的复用率极低。例如,当项目中的控件 对圆角 shape 有新的规格要求时,哪怕不同规格之间仅仅是 圆角 dp 存在细微差异,也不能像动态代码那样 通过修改参数即可完成,而是需要 重新创建一个新的 shape Drawable 文件。

2.XML 的解析由 LayoutInflater 负责,LayoutInflater 是通过 深度优先遍历 算法解析 XML 来 构建视图树,从而当布局嵌套层次加深等原因导致的 View 个数过多时,XML 的解析就会愈加耗时。

include,merge,ViewStub 是解药的解药

所以 XML 布局 其实只是一种 充分非必要 的 构建视图树的方式。

我们看到许多 东拼西凑 的性能优化网文,不分场合地兜售 include,merge,ViewStub,事实上 它们仅仅是用于 XML 布局的情况 —— 通过减少层级来 解决 inflate 耗时问题

Telegram 源码 中,这种 全动态编码 的布局构建方式,就完全用不上 从 LayoutInflater 到 ViewStub 的这些技术。

划重点 👆 👆 👆

作为承上启下的小结

所以到此为止,对 "Window" & Canvas;View & Drawable;Layout & Inflater;merge & ViewStub 等 技术点 层层递进的关系,是不是也就做到 心中有数 了呢?

是的,无论 Button、TextView、ImageView,还是 MaterialButton、TextInputLayout 等等,无论其表面如何千变万化,都不过是 为了在特定场景下 符合用户直觉 而衍生出的 新的、具体的 排版模板,本质上都是 基于 Canvas 去绘制 特定样式的 可视化内容

而 Inflate 也不过是发生在 当开发者选择通过 “声明式编程” 而非 “动态编码” 的方式 去构建视图树。所以若非使用 LayoutInflater,开发者甚至无须知道 include,merge,ViewStub 的存在。

好,经过上文这样分析和梳理一遍,相信大家已对 “View 体系” 的结构耳清目明,

因而下文我们继续专注于 让一些原本令人觉得 “复杂”、“避之不及” 的混沌系统 变得 “清晰”、“可控”,以便 读者们 “有灵感、有方向” 觉得可以深入、得以自行将 “网上现成的优质资源” 物尽其用。

试读内容完。

版权声明

Copyright © 2019-present KunMinX

本文为专栏的试读内容,转载须注明作者、链接出处、并保持原文完整(即包含从 “前言” 到 “本声明” 在内的所有内容)

文中 原创的引言、切入点、思路、结论 凝聚了作者 KunMinX 本人的心血,作者本人对相应的成果享有所有权。

当您 借鉴或引用文中的引言、切入点、思路、结论 进行二次创作并打算发行时,须注明链接出处,否则我们保留追责的权利。对此如有疑虑请及时沟通,或在发行前将作品交由本人核对。

当您看见有人 洗稿、剽窃、未符合要求地转载本文内容时,请及时向本人举报。

最后感谢您对本文的阅读和喜欢。

最后更新于