杨凡 About

在知乎的 2018 年

今年是在知乎的第二年。

首先得提及下我的前 Leader 佳恒(目前已去美国),他是个十分 Nice 的人,也是知乎里对我帮助最大的人。今年夏季,他把我从业务组调到了他 Lead 的「Android 基础架构组」,这个组属于中台部门,主要负责提升工程师团队的开发效率、质量,国外有个很火的词「DevOps」指的就是这个方向~

实际上,在知乎里做 DevOps 十分具有挑战性。庞大的开发人员数量和 Codebase 规模都让开发效率和质量的保障变得很困难。而且作为一个有着上亿用户、千万级 DAU 的产品,开发时看似很小的问题一旦到了线上都可能会被放大而导致公司损失惨重 w(゚Д゚)w。所以非常需要中台提供流程、工具、自动化来解决这些问题。

接下来,就记录一下我在这个组里做过的一些有意义或有趣的事~

Maven 仓库

随着组件化的推行,Android 组变得严重依赖于 Maven 仓库。虽然公司内部已经搭建了一个 JFrog 仓库,但是由于管理权限不在我们组,想自定义或遇到问题想解决时都十分困难,所以我们组计划自己搭建一个 Maven 仓库。

由于当时组里只有我有过搭建 Maven 的经验,所以这个任务自然而然就落到了我的手上 ㄟ( ▔, ▔ )ㄏ 。于是用 Docker,配合公司内网 Dns、Nginx-proxy 快速搭了个 Nexus 仓库服务,并且代理了所有用到的第三方的远程仓库,然后把我们 Gradle 工程的 Maven 源依赖都收缩到我们自己搭建的仓库上,这样就能很方便地集中管理我们的 Maven 源和所有 Artifact 了。

JarFilter

这其实是我的个人 开源项目 但目前已经用在了知乎上,想更详细了解的话可以看这篇 文章

简单来讲,这个 Gradle 插件可以在编译时把指定 Jar 里的指定 Class 文件剔除掉。这么做主要为了能快速修改某个第三方库里类的实现:

无需 Fork 或 Clone 第三方库的所有源码,只需复制目标类的源码到主工程(通常在 IDE 中可以直接从 Sources.jar 中复制),然后用该插件剔除掉原 Jar 中的 Class 然后一起编译即可。

在知乎里,也是使用该插件来替换一些第三方库里的部分实现,例如在 OkHttp 库中添加特殊逻辑。

TaskManager

上古时期留下来的 Codebase 并没有对启动代码进行管理,导致了启动时各种初始化的代码都堆在了某几个方法里 =_=。

而随着组件化的推行,这些代码需要被分到不同的组件工程中。但是糟糕的是,这些代码间可能有着错综复杂的依赖关系,例如 E 的初始化可能依赖 C 的初始化,而 C 的初始化又同时依赖 A、B 的初始化。

于是我参考了 Gradle 的设计,为这些代码提供了一个载体「Task」,并为载体间提供 dependsOn()finalizedBy() 等方法来描述依赖关系。

于是,这些启动 Task 最终可以梳理成一棵棵的依赖树:

实线表示 dependsOn(),虚线表示 finalizedBy()。这棵树上的连线越多表示这棵树越健壮,这个时候就可以通过中央管理器 TaskManager 把这颗树「优化」后扔到不同的线程去并发执行。

在 TaskManager 里我还实现了个比较有趣的 Feature,就是在组件工程中可以「自注册」Task。主工程(注意,是主工程)在编译时会收集组件 Jar 中所有以 T_ 开头的 Task Class,并生成对应的字节码在运行时自动把这些 Task 注册进 TaskManager。这样就可以实现在组件工程添加 Task 的时候无需改动主工程的代码。

目前,在知乎工程中,已通过 TaskManager 在各个组件中创建了三十多个 Task 。

启动优化

这是今年工作中的重心,上面的 TaskManager 其实也是在为这个任务做准备~但这并不是一件容易的事,知乎的启动流程十分复杂,想要进行优化的话要对整个启动流程进行详细的 Profile 和梳理。

之前以公司名义发布过一篇 文章,里面写了我使用 Traceview 、Systrace 等各种工具进行 Profile 的过程。实际上整个第三季度,经过几个版本的连续优化之后成功缩短了将近 50% 的启动时间。

除此之外,为了监控以后的代码改动,避免某个新 Feature 的引入影响到启动时间,我还开发了启动时间自动化测试系统并集成进 Jenkins 中:

该系统会对 MR 源分支中的代码进行编译并推送到远程手机上进行自动化测试,最后收集启动相关的数据并评论到 MR 上。

至此第三季度启动优化的工作就告一段落了。实际上,公司十分重视这次工作,在结项时还发了针对该次项目的纪念品给我们 (=。=)

Dependency Graph

代码间互相依赖的问题一直导致组件化过程受阻,为此我用 AntV 对主工程的各个库之间的依赖关系进行了可视化,方便在组件化时理清各组件间的关系:

有兴趣的话可以看看这个 Live Demo。目前在知乎中,只要往主工程合入新的代码即会触发我写的脚本来更新该依赖图。

DepAn

DepAn 是一个依赖分析系统,也是我第四季度的主要工作。它可以对 MR 进行分析,找出所有会影响到其他仓库代码的改动,然后评论到 MR 上并 @ 指定的仓库负责人:

该系统的核心是在编译时对字节码进行分析,这部分是参考 Google depan 项目的逻辑修改而来的,目前已剥离出最基础的代码 反哺社区

12 月末该系统成功上线,目前已集成进半数的仓库中,并已帮助工程师、测试人员进行了数百次的 MR 影响范围分析 ヾ( ̄▽ ̄)。此外,其实该系统更重要的意义是建立起了知乎 Android 整个工程的字节码数据库,为未来所有基于字节码分析的技术提供了基础~

最后

没想到时间过得这么快,不知不觉就在知乎呆了快两年了,真的很庆幸能在这个平台上学习到很多东西,也很感激这个平台给我机会能独立完成这么多有挑战性的工作。

希望知乎未来能够越来越好吧~