工作多年,进阶 “细节定制”,难免高频接触 “自定义 View”、“GUI 性能优化” 等概念,

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

关于 View 体系,开发者 普遍缺乏 “从 0 到 1 完整踩坑” 经历,且多数网文假设读者已有 前置知识 铺垫在先,因而 直奔 “What、How” 细枝末节,对 “Why” 绝口不提

这使文章读起来 “没头没尾”,让人知难而退。

故这期我们继续 “追根溯源”,铺垫 Android GUI 系统的来龙去脉,相信阅读后,你会对 “为何要学这些技术点、每个技术的「作用边界」到底从哪到哪、技术之间的「关系和顺序」又该如何串起来” 形成清晰理解。

注:本文节选自《重学安卓:过目难忘 Android GUI 关系梳理》,本文可免费阅读和全文转载,转载时须注明本文链接出处。

文章目录一览

  • 前言
  • 拆解思路
    • Canvas 是排版的根基
    • View 和 Drawable 是排版的模板
    • Layout 和 Inflater 不过是后来者
    • include,merge,ViewStub 是解药的解药
  • 作为承上启下的小结
    • 上文 “Window” 为何一直加个双引号
    • ViewRootImpl 是怎么帮 Canvas 与窗口对应上的
    • 对症下药 “排版渲染” 性能优化指南
    • PhoneWindow 本质 及 事件分发内幕
  • 综上

Canvas 是排版的根基

所谓排版,即是将一个个图形化内容,以符合预期的方式组合排列,包括并列展示和层叠展示,供用户更好的浏览,

根据《重学安卓:Activity 快乐你不懂》一文的推理,在从零开始缔造的原始操作系统中,UI 排版的工作最初是由 Window 承担,Window 不堪重负,于是责任下发到 View,通过 View/ViewGroup 在 View 树中的递归,以便更快更好完成,

那么是否意味着 View 即是排版的根基?答案是否定的,

因为 View 只是起到 “承载内容、且限制内容展示范围” 的作用,例如做过 “添加水印” 的小伙伴应该都接触过 “贴图控件”,该控件的原理是,在同一个自定义 View 中绘制多个贴图元素,那么显而易见 View 并不是排版的最小单位,被绘制的元素才是,

img

故而背后的画师 Canvas,才是排版的根基:

排版依赖于绘制 —— 在排版这件事上,Canvas 可以没有 View,但 View 不能没有 Canvas。

Paint 和 Path 都是 Canvas 的助手,与 “排版输出” 存在直接关系的 “最源头 API”,即是 Canvas 本尊。

View 和 Drawable 是排版的模板

既然是 View 系统,那就不能只是原始、无序的 “绘制” 本身。

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

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

且,如对 “现成模板” 不满意,也可自行封装新模板,也即人们所说 “自定义控件”。

与此同时,View 只是模板,如需更改可视化细节,岂不是又要接触 Canvas,

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

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

使得多数情况下,我们都能直接使用 “Drawable 模板” 描述排版细节,避免良莠不齐开发者 “直接与 Canvas 打交道导致不可预知隐患”。

划重点 👆 👆 👆

16、20、32dp 圆角 16、20dp 圆角 16dp 圆角
img img img

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

Layout 和 Inflater 不过是后来者

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

iOS 开发者常抱怨,在 iOS 上写布局,无预览,全靠想象,

为此,Google 模仿微软 WPF 设计,通过 XML 方式实现 一目了然 “声明式编程”可实时预览 Layout、shape、selector 乃至极大方便 Android 开发者创建和修改可视化内容。

注:Layout 对应 View 树;shape、selector 对应 Drawable。

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

1.XML 排版资源复用率极低。例如,为不同场景 Button 改圆角样式,无法像动态代码那样,直接代码中定义参数。哪怕不同样式间仅是 “圆角 dp” 存在细微差异,也得重建一个新 shape Drawable XML 描述文件。

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

include,merge,ViewStub 是解药的解药

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

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

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

划重点 👆 👆 👆

作为承上启下的小结

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

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

至此,”Window” & Canvas;View & Drawable;Layout & Inflater;merge & ViewStub,来龙去脉一目了然。

img

试读内容完。

相关资料

重学安卓:过目难忘 Android GUI 关系梳理

版权声明

Copyright © 2019-present KunMinX 原创版权所有。

如需 转载本文,或引用、借鉴 本文 “引言、思路、结论、配图” 进行二次创作发行,须注明链接出处,否则我们保留追责权利。