mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-17 14:43:42 +08:00
mod
This commit is contained in:
112
极客时间专栏/Android开发高手课/模块二 高效开发/25 | 如何提升组织与个人的研发效能?.md
Normal file
112
极客时间专栏/Android开发高手课/模块二 高效开发/25 | 如何提升组织与个人的研发效能?.md
Normal file
@@ -0,0 +1,112 @@
|
||||
<audio id="audio" title="25 | 如何提升组织与个人的研发效能?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a2/cb/a28b668c52d9f64d29cfa82f362804cb.mp3"></audio>
|
||||
|
||||
通过“高质量开发”模块的学习,相信你已经对打造一款高质量应用信心满满了。不过人们常说“提质增效”,总喜欢把质量和效率联系在一起,我们都希望在保证质量的前提下,为自己的团队提速。
|
||||
|
||||
特别是移动互联网在红海厮杀的今天,快速试错变得越来越重要,敏捷开发也被越来越多的团队所推崇。有些时候为了效率我们甚至愿意牺牲部分性能,而选择在合适的时间去偿还这些“债务”。
|
||||
|
||||
在“高质量开发”模块中,我侧重如何给应用交付的每个步骤做好“质检”。今天我们就一起来开启新的征程,从组织和个人研发效能的角度,重新帮你审视整个应用交付的过程。
|
||||
|
||||
## 组织的研发效能
|
||||
|
||||
**1. 何为研发效能**
|
||||
|
||||
在讨论如何优化组织研发效能之前,请你先思考一下什么是研发效能。
|
||||
|
||||
我们平常开发的过程,是从产品的一个需求想法,转变为功能并且发布上线。这个过程会涉及产品、设计、开发、测试,更多的时候可能还会拉上前端、后台或者算法。
|
||||
|
||||
产品的交付涉及很多的流程和人员,虽然设计人员出图很快、我们开发效率很高,但也并不能代表研发效能同样很高,研发效能是对整个产品最终交付的速度和质量负责。在[《如何衡量研发效能》](https://mp.weixin.qq.com/s/vfhqRxLnHJz_ii2zhXofuA)一文中,将研发效能定义为**一个组织持续快速交付价值的能力**。
|
||||
|
||||
在文中,作者从流动效率、资源效率和质量进一步拆解了研发效能,并提出了研发效能的五个衡量标准。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8a/4b/8aee56bf643584c04582fb09a98c3c4b.png" alt="">
|
||||
|
||||
对于客户端研发来说,我们是不是只要保证按时按质实现需求就可以了呢?有很多公司,尽管实行“996”,产品、开发和测试看起来的确都很忙了,但是交付速度和质量却仍然不令人满意:产品埋怨开发效率低、开发埋怨产品需求不明确、测试埋怨开发质量差、开发埋怨测试发现不了问题等。
|
||||
|
||||
这是因为什么呢?对于研发效能这个话题,虽然我并不是这个领域的专家,但根据我多年的工作经验,我可以谈谈个人的两点思考:
|
||||
|
||||
<li>
|
||||
**提效是每个人的职责**。尽管在BAT这些大厂,会有专门的研发效能部门,但是效能的提升并不是单单只依靠效能部门,或者认为是领导的事情,而是组织里面每一个人都应该去思考的事情。例如天猫设立的效能目标是“211”,也就是2周交付周期、1周开发周期以及1小时发布时长。那团队中的每一个人都应该为这一共同目标而努力,回顾在每个发布迭代中遇到的问题以及改进的建议。
|
||||
</li>
|
||||
<li>
|
||||
**提效不仅限于写Android代码**。尽管我们是Android开发工程师,但是我们的工作不应该局限在写Android代码上,关键还是解决需求场景。无论是APM系统、大网络平台、大数据平台,这种大型的前后端一体化解决方案,还是需求流程上或者增强产研测沟通信任的优化。只要是对提升效能有帮助的,我们都可以尝试去实践。**在建立了这种整体统筹的思维之后,未来我们想转后端、前端,甚至是产品都会有很大的帮助**。
|
||||
</li>
|
||||
|
||||
微信在很早的时候就引入了Google的[OKR](https://www.zhihu.com/question/22471467)绩效考核制度,记得在2017年Android团队定了“质量”“效能”和“影响力”这三个目标。
|
||||
|
||||
接着Android团队的30多个研发也都在围绕这三个目标来制定自己的工作计划,例如针对“效能”来说,有的人抽离一个UI库或者动画库,有的人写一个监控工具,有的人提升编译速度,有的人写一个Web的值班页面,有的人优化需求评审的流程…
|
||||
|
||||
这样大家集思广益,一起思考、一起讨论,为达成组织的共同目标而努力,这也是为什么微信开发人员虽然不多,但是战斗力在业界数一数二的原因。
|
||||
|
||||
**2. 应用交付的流程**
|
||||
|
||||
前面我从整个组织的角度,定义了研发效能的含义以及衡量它的五个标准。同时也结合我在微信的经历,谈了我关于提升研发效能的两点思考。可能大部分同学还是感觉整个产品的交付流程类似产品、UI设计这些环节是研发人员无法把控的,那接下来我只从研发的流程来看如何提高效能。
|
||||
|
||||
正如我在专栏导读[《如何打造高质量的应用》](https://time.geekbang.org/column/article/70250)所说的,一个应用至少会经过开发、编译CI、测试、灰度和发布这几个阶段。下面我从效能的角度,分别看看每个阶段需要关注什么问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/de/bf/de7478cd66a44d2807e1c89a3a26bbbf.jpg" alt="">
|
||||
|
||||
<li>
|
||||
**开发阶段**。开发阶段解决的是如何用尽可能短的时间,完成尽可能多的需求,并且保证开发的质量,不至于后期过多的返工。项目的架构应该如何选择?例如应该采用原生开发,还是Web、React Native、Flutter这样的跨平台方案。如何提升团队人员的能力以及工具和框架的成熟度?有哪些提升团队工作效率的技巧?
|
||||
</li>
|
||||
<li>
|
||||
**编译、CI阶段**。编译CI阶段解决的是如何发现和优化开发阶段的一些编码问题,以及快速构建出最终产物。Google的Gradle、Facebook的Buck为编译速度做了哪些努力?Flutter的Hot Reload为什么可以这么快?AspectJ、ASM、ReDex这三种插桩方法的原理和差别是什么?腾讯的RDM、阿里的摩天轮这些编译构建平台有什么特别之处?
|
||||
</li>
|
||||
<li>
|
||||
**测试阶段**。测试阶段是为了发现交付过程的质量问题。测试的确不容易,自动化测试成本高,也不容易把控发布质量。那如何可以让测试覆盖更多的场景,Monkey、性能测试、UI测试应该怎么实践?腾讯的RDM、蚂蚁的伙伴是如何做到人人都是测试?网易的Airtest测试框架有哪些过人之处?
|
||||
</li>
|
||||
<li>
|
||||
**灰度、发布阶段**。灰度发布是为了验证产品的效果。发布并不是把包丢出去就可以了,我们需要对自己的产品负责。那如何准确、快速地评估产品数据?头条、快手是如何做到精准运营和A/B测试?如果遇到疑难的线上问题应该怎么解决?复杂多变的网络问题又应该怎样去定位和分析?
|
||||
</li>
|
||||
|
||||
当然为了提升在这个过程的效率,我们会用到一些很有用的第三方工具,例如用于CodeReview的Gerrit、持续集成的Jenkins、代码审计的Coverity等。
|
||||
|
||||
工具不仅可以将大量人工操作变成自动化,也可以方便团队更好的协作。例如我们把需求的流转、进度安排变得可视化,可以大大地减少产研测团队之间消息的隔阂,在这方面阿里的AONE或者腾讯的TPAD都做得非常不错。
|
||||
|
||||
项目管理、需求管理、代码托管、构建/部署、测试平台…都是我们常用的工具,类似阿里的[云效](http://cn.aliyun.com/product/yunxiao)会提供这样的一站式平台,从需求发起到分支管理、代码review,再到测试发布。在过去,这些工具都是各大公司研发效能部门多年的结晶,一般都不愿意对外提供。但是得益于云时代、TOB时代的到来,现在都愿意打包成商品供我们使用。
|
||||
|
||||
当然每个项目都会有自己特殊的情况,这些工具也不一定可以完全符合我们的需要,我们可以根据自己的情况选择合适的服务,或者直接开发自己的工具。
|
||||
|
||||
## 个人的研发效率
|
||||
|
||||
个人作为整个组织的一部分,我们效率的提升也会对组织有正向的作用。特别是对某些小团队或者独立开发者来说,个人可能就代表了整个团队。
|
||||
|
||||
关于个人效率的提升和时间的管理,有很多书籍专门在讲这个内容。下面从我看到的一些不好的现象,谈谈我个人两个比较深的体会。
|
||||
|
||||
**1. 思考**
|
||||
|
||||
>
|
||||
年轻人千万不要碰的东西之一,便是能获得短期快感的软件。它们会在不知不觉中偷走你的时间,消磨你的意志力,摧毁你向上的勇气。
|
||||
|
||||
|
||||
随着我们接触到的信息越来越多,越来越多的人很难保持对事情的专注力。工作期间经常想着去刷一下抖音、头条、微信、王者荣耀,强行把时间打破成碎片。
|
||||
|
||||
>
|
||||
跟产品开了一天的会,他的需求有了,你的代码呢?
|
||||
|
||||
|
||||
可能也有一部分同学他们不刷抖音和头条,但是在上班时间也会被各种邮件、钉钉、会议折磨得痛不欲生。针对这个问题,我的做法是每天上午和下午都会至少保留一个小时“目空一切”的时间,不看邮件、不看钉钉、不接会议。当然有的时候也是无法避免被老板当面拉回到“现实”。
|
||||
|
||||
我经常看到团队里面的一些人也存在这种现象,最终表现可能是这个人经常“996”,看起来很忙,但是产出并不高,而且个人成长也不明显。
|
||||
|
||||
每天我们应该需要有一段时间真正的静下心来工作,而且每过一段时间也要重新审视一下自己的工作,有哪些地方做的不够好?有没有什么事情是自己或者团队的人正在反复而低效在做的,是否可以优化。
|
||||
|
||||
**2. 方法**
|
||||
|
||||
关于方法,这里我只说两点,也是同学们经常会出现的问题。
|
||||
|
||||
<li>
|
||||
**做事的方法**。曾经看到一些开发人员,非常喜欢用二分法来排查问题。当测试给他报Bug时,他会非常熟练的操作Git命令,花上一两个小时打出几十个验证包,不辞劳苦地尝试。当然二分法我也使用过,在毫无头绪的时候的确可以“死马当活马医”。但是我们在使用这个“大杀器”之前,起码应该经过自己的思考,尝试正面去迎击Bug本身。
|
||||
</li>
|
||||
<li>
|
||||
**提问的方法**。在微信和QQ群里面,经常会看到有些同学在群里问一个问题,可能Google一下就可以得到答案。然后他们在群里灌了一个小时水,最后还是没有任何答案。在做Tinker开源的时候,我有时也被一些使用者问得心情不再愉悦。事实上提问题是非常体现技术和职业素养的,我们在提问题之前需要经过自己的思考和努力,在这里推荐你看看[《提问的艺术》](https://github.com/tvvocold/How-To-Ask-Questions-The-Smart-Way)。
|
||||
</li>
|
||||
|
||||
## 总结
|
||||
|
||||
“吾日三省吾身”,无论是组织的研发效能,还是个人的工作效率,我们都需要学会经常去回顾和思考,快速演进、快速迭代,争取未来做得更好。
|
||||
|
||||
你在工作和学习的效率上,遇到过哪些问题?对于如何提升工作和学习的效率,你还有什么好的方法和建议吗?欢迎留言分享给我和其他同学。
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。我也为认真思考、积极分享的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
250
极客时间专栏/Android开发高手课/模块二 高效开发/26 | 关于编译,你需要了解什么?.md
Normal file
250
极客时间专栏/Android开发高手课/模块二 高效开发/26 | 关于编译,你需要了解什么?.md
Normal file
@@ -0,0 +1,250 @@
|
||||
<audio id="audio" title="26 | 关于编译,你需要了解什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c2/db/c2aa66efc2e5edd49b7a9ea0c0281edb.mp3"></audio>
|
||||
|
||||
作为Android工程师,我们每天都会经历无数次编译;而对于大型项目来说,每次编译就意味着要花去一杯咖啡的时间。可能我讲具体的数字你会更有体会,当时我在微信时,全量编译Debug包需要5分钟,而编译Release包更是要超过15分钟。
|
||||
|
||||
如果每次编译可以减少1分钟,对微信整个Android团队来说就可以节约1200分钟(团队40人 × 每天编译30次 × 1分钟)。所以说优化编译速度,对于提升整个团队的开发效率是非常重要的。
|
||||
|
||||
那应该怎么样优化编译速度呢?微信、Google、Facebook等国内外大厂都做了哪些努力呢?除了编译速度之外,关于编译你还需要了解哪些知识呢?
|
||||
|
||||
## 关于编译
|
||||
|
||||
虽然我们每天都在编译,那到底什么是编译呢?
|
||||
|
||||
你可以把编译简单理解为,将高级语言转化为机器或者虚拟机所能识别的低级语言的过程。对于Android来说,这个过程就是把Java或者Kotlin转变为Android虚拟机运行的[Dalvik字节码](https://source.android.com/devices/tech/dalvik/dalvik-bytecode)的过程。
|
||||
|
||||
编译的整个过程会涉及词法分析、语法分析 、语义检查和代码优化等步骤。对于底层编译原理感兴趣的同学,你可以挑战一下编译原理的三大经典巨作:[龙书、虎书、鲸鱼书](https://www.itcodemonkey.com/article/3521.html)。
|
||||
|
||||
但今天我们的重点不是底层的编译原理,而是希望一起讨论Android编译需要解决的问题是什么,目前又遇到了哪些挑战,以及国内外大厂又给出了什么样的解决方案。
|
||||
|
||||
**1. Android编译的基础知识**
|
||||
|
||||
无论是微信的编译优化,还是Tinker项目,都涉及比较多的编译相关知识,因此我在Android编译方面研究颇多,经验也比较丰富。Android的编译构建流程主要包括代码、资源以及Native Library三部分,整个流程可以参考官方文档的[构建流程图](https://developer.android.com/studio/build/?hl=zh-cn)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7e/87/7e66559bb42ce21a538ab34f596c1f87.png" alt="">
|
||||
|
||||
[Gradle](https://docs.gradle.org/current/userguide/userguide.html)是Android官方的编译工具,它也是GitHub上的一个[开源项目](https://github.com/gradle/gradle)。从Gradle的[更新日志](https://gradle.org/releases/)可以看到,当前这个项目还更新得非常频繁,基本上每一两个月都会有新的版本。对于Gradle,我感觉最痛苦的还是Gradle Plugin的编写,主要是因为Gradle在这方面没有完善的文档,因此一般都只能靠看源码或者[断点调试](https://fucknmb.com/2017/07/05/%E5%8F%88%E6%8E%8C%E6%8F%A1%E4%BA%86%E4%B8%80%E9%A1%B9%E6%96%B0%E6%8A%80%E8%83%BD-%E6%96%AD%E7%82%B9%E8%B0%83%E8%AF%95Gradle%E6%8F%92%E4%BB%B6/)的方法。
|
||||
|
||||
但是编译实在太重要了,每个公司的情况又各不相同,必须强行造一套自己的“轮子”。已经开源的项目有Facebook的[Buck](https://github.com/facebook/buck)以及Google的[Bazel](https://github.com/bazelbuild/bazel)。
|
||||
|
||||
为什么要自己“造轮子”呢?主要有下面几个原因:
|
||||
|
||||
<li>
|
||||
**统一编译工具**。Facebook、Google都有专门的团队负责编译工作,他们希望内部的所有项目都使用同一套构建工具,这里包括Android、Java、iOS、Go、C++等。编译工具的统一优化,所有项目都会受益。
|
||||
</li>
|
||||
<li>
|
||||
**代码组织管理架构**。Facebook和Google的代码管理有一个非常特别的地方,就是整个公司的所有项目都放到同一个仓库里面。因此整个仓库非常庞大,所以他们也不会使用Git。目前Google使用的是[Piper](http://www.ruanyifeng.com/blog/2016/07/google-monolithic-source-repository.html),Facebook是基于[HG](https://www.mercurial-scm.org/)修改的,也是一种基于分布式的文件系统。
|
||||
</li>
|
||||
<li>
|
||||
**极致的性能追求**。Buck和Bazel的性能的确比Gradle更好,内部包含它们的各种编译优化。但是它们或多或少都有一些定制的味道,例如对Maven、JCenter这样的外部依赖支持的也不是太好。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fa/90/fa5da989a8047e935ec15803235ecd90.png" alt="">
|
||||
|
||||
“程序员最痛恨写文档,还有别人不写文档”,所以它们的文档也是比较少的,如果想做二次定制开发会感到很痛苦。如果你想把编译工具切换到Buck和Bazel,需要下很大的决心,而且还需要考虑和其他上下游项目的协作。当然即使我们不去直接使用,它们内部的优化思路也非常值得我们学习和参考。
|
||||
|
||||
Gradle、Buck、Bazel都是以更快的编译速度、更强大的代码优化为目标,我们下面一起来看看它们做了哪些努力。
|
||||
|
||||
**2. 编译速度**
|
||||
|
||||
回想一下我们的Android开发生涯,在编译这件事情上面究竟浪费了多少时间和生命。正如前面我所说,编译速度对团队效率非常重要。
|
||||
|
||||
关于编译速度,我们最关心的可能还是编译Debug包的速度,尤其是**增量编译**(incremental build)的速度,希望可以做到更加快速的调试。正如下图所示,我们每次代码验证都要经过编译和安装两个步骤。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/04/56/04a94858fd23dc6d1ccce64f400c2656.png" alt="">
|
||||
|
||||
<li>
|
||||
**编译时间**。把Java或者Kotlin代码编译为“.class“文件,然后通过dx编译为Dex文件。对于增量编译,我们希望编译尽可能少的代码和资源,最理想情况是只编译变化的部分。但是由于代码之间的依赖,大部分情况这并不可行。这个时候我们只能退而求其次,希望编译更少的模块。[Android Plugin 3.0](https://developer.android.com/studio/build/dependencies)使用Implementation代替Compile,正是为了优化依赖关系。
|
||||
</li>
|
||||
<li>
|
||||
**安装时间**。我们要先经过签名校验,校验成功后会有一大堆的文件拷贝工作,例如APK文件、Library文件、Dex文件等。之后我们还需要编译Odex文件,这个过程特别是在Android 5.0和6.0会非常耗时。对于增量编译,最好的优化是直接应用新的代码,无需重新安装新的APK。
|
||||
</li>
|
||||
|
||||
对于增量编译,我先来讲讲Gradle的官方方案[Instant Run](https://developer.android.com/studio/run/?hl=zh-cn)。在Android Plugin 2.3之前,它使用的Multidex实现。在Android Plugin 2.3之后,它使用Android 5.0新增的Split APK机制。
|
||||
|
||||
如下图所示,资源和Manifest都放在Base APK中, 在Base APK中代码只有Instant Run框架,应用的本身的代码都在Split APK中。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/11/a5/118f836da894cdc9ff16675293e44aa5.png" alt="">
|
||||
|
||||
Instant Run有三种模式,如果是热交换和温交换,我们都无需重新安装新的Split APK,它们的区别在于是否重启Activity。对于冷交换,我们需要通过`adb install-multiple -r -t`重新安装改变的Split APK,应用也需要重启。
|
||||
|
||||
虽然无论哪一种模式,我们都不需要重新安装Base APK。这让Instant Run看起来是不是很不错,但是在大型项目里面,它的性能依然非常糟糕,主要原因是:
|
||||
|
||||
<li>
|
||||
**多进程问题**。“The app was restarted since it uses multiple processes”,如果应用存在多进程,热交换和温交换都不能生效。因为大部分应用都会存在多进程的情况,Instant Run的速度也就大打折扣。
|
||||
</li>
|
||||
<li>
|
||||
**Split APK安装问题**。虽然Split APK的安装不会生成Odex文件,但是这里依然会有签名校验和文件拷贝(APK安装的乒乓机制)。这个时间需要几秒到几十秒,是不能接受的。
|
||||
</li>
|
||||
<li>
|
||||
**javac问题**。在Gradle 4.6之前,如果项目中运用了Annotation Processor。那不好意思,本次修改以及它依赖的模块都需要全量javac,而这个过程是非常慢的,可能会需要几十秒。这个问题直到[Gradle 4.7](https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing)才解决,关于这个问题原因的讨论你可以参考这个[Issue](https://github.com/gradle/gradle/issues/1320)。
|
||||
</li>
|
||||
|
||||
你还可以看看这一个Issue:“[full rebuild if a class contains a constant](https://github.com/gradle/gradle/issues/2767)”,假设修改的类中包含一个“public static final”的变量,那同样也不好意思,本次修改以及它依赖的模块都需要全量javac。这是为什么呢?因为常量池是会直接把值编译到其他类中,Gradle并不知道有哪些类可能使用了这个常量。
|
||||
|
||||
询问Gradle的工作人员,他们出给的解决方案是下面这个:
|
||||
|
||||
```
|
||||
// 原来的常量定义:
|
||||
public static final int MAGIC = 23
|
||||
|
||||
// 将常量定义替换成方法:
|
||||
public static int magic() {
|
||||
return 23;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
对于大型项目来说,这肯定是不可行的。正如我在Issue中所写的一样,无论我们是不是真正改到这个常量,Gradle都会无脑的全量javac,这样肯定是不对的。事实上,我们可以通过比对这次代码修改,看看是否有真正改变某一个常量的值。
|
||||
|
||||
但是可能用过阿里的[Freeline](https://github.com/alibaba/freeline)或者蘑菇街的[极速编译](https://tech.meili-inc.com/233-233?from=timeline&isappinstalled=0)的同学会有疑问,它们的方案为什么不会遇到Annotation和常量的问题?
|
||||
|
||||
事实上,它们的方案在大部分情况比Instant Run更快,那是因为牺牲了正确性。也就是说它们为了追求更快的速度,直接忽略了Annotation和常量改变可能带来错误的编译产物。Instant Run作为官方方案,它优先保证的是100%的正确性。
|
||||
|
||||
**当然Google的人也发现了Instant Run的种种问题,在Android Studio 3.5之后,对于Android 8.0以后的设备将会使用新的方案“[Apply Changes](https://androidstudio.googleblog.com/2019/01/android-studio-35-canary-1-available.html)”代替Instant Run。目前我还没找到关于这套方案更多的资料,不过我认为应该是抛弃了Split APK机制**。
|
||||
|
||||
一直以来,我心目中都有一套理想的编译方案,这套方案安装的Base APK依然只是一个壳APK,真正的业务代码放到Assets的ClassesN.dex中。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f0/e4/f02eeaf46a8a4c5a7f82544ca208ebe4.png" alt="">
|
||||
|
||||
<li>
|
||||
**无需安装**。依然使用类似Tinker热修复的方法,每次只把修改以及依赖的类插入到pathclassloader的最前方就可以,不熟悉的同学可以参考[《微信Android热补丁实践演进之路》](https://mp.weixin.qq.com/s/-NmkSwZu83HAmzKPawdTqQ)中的Qzone方案。
|
||||
</li>
|
||||
<li>
|
||||
**Oatmeal**。为了解决首次运行时Assets中ClassesN.dex的Odex耗时问题,我们可以使用“安装包优化“中讲过的ReDex中的黑科技:Oatmeal。它可以在100毫秒以内生成一个完全解释执行的Odex文件。
|
||||
</li>
|
||||
<li>
|
||||
**关闭JIT**。我们通过在AndroidManifest指定[android:vmSafeMode=“true”](https://developer.android.com/guide/topics/manifest/application-element?hl=zh-cn#vmSafeMode),关闭虚拟机的JIT优化,这样也就不会出现Tinker在[Android N混合编译遇到的问题](https://mp.weixin.qq.com/s/h9BHnEkV0RMx0yIW1wpw9g)。
|
||||
</li>
|
||||
|
||||
这套方案应该可以完全解决Instant Run当前的各种问题,我也希望对编译优化感兴趣的同学可以自行实现这一套方案,并能开源出来。
|
||||
|
||||
对于编译速度的优化,我还有几个建议:
|
||||
|
||||
<li>
|
||||
**更换编译机器**。对于实力雄厚的公司,直接更换Mac或者其他更给力的设备作为编译机,这种方式是最简单的。
|
||||
</li>
|
||||
<li>
|
||||
**Build Cache**。可以将大部分不常改变的项目拆离出去,并使用[远端Cache](http://docs.gradle.com/enterprise/tutorials/caching/)模式保留编译后的缓存。
|
||||
</li>
|
||||
<li>
|
||||
**升级Gradle和SDK Build Tools**。我们应该及时去升级最新的编译工具链,享受Google的最新优化成果。
|
||||
</li>
|
||||
<li>
|
||||
**使用Buck**。无论是Buck的exopackage,还是代码的增量编译,Buck都更加高效。但我前面也说过,一个大型项目如果要切换到Buck,其实顾虑还是比较多的。在2014年初微信就接入了Buck,但是因为跟其他项目协作的问题,导致在2015年切换回Gradle方案。相比之下,**可能目前最热的Flutter中[Hot Reload](https://juejin.im/post/5bc80ef7f265da0a857aa924)秒级编译功能会更有吸引力**。
|
||||
</li>
|
||||
|
||||
当然最近几个Android Studio版本,Google也做了大量的其他优化,例如使用[AAPT2](https://developer.android.com/studio/command-line/aapt2)替代了AAPT来编译Android资源。AAPT2实现了资源的增量编译,它将资源的编译拆分成Compile和Link两个步骤。前者资源文件以二进制形式编译Flat格式,后者合并所有的文件再打包。
|
||||
|
||||
除了AAPT2,Google还引入了d8和R8,下面分别是Google提供的一些测试数据。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/16/ed5dd1797ea15e58c4bbe5a52008bd16.png" alt=""><br>
|
||||
<img src="https://static001.geekbang.org/resource/image/cd/2a/cd479426df64c979cda4fe35578c9f2a.png" alt="">
|
||||
|
||||
那什么是d8和R8呢?除了编译速度的优化,它们还有哪些其他的作用?
|
||||
|
||||
**3. 代码优化**
|
||||
|
||||
对于Debug包编译,我们更关心速度。但是对于Release包来说,代码的优化更加重要,因为我们会更加在意应用的性能。
|
||||
|
||||
下面我就分别讲讲ProGuard、d8、R8和ReDex这四种我们可能会用到的代码优化工具。
|
||||
|
||||
**ProGuard**
|
||||
|
||||
在微信Release包12分钟的编译过程里,单独ProGuard就需要花费8分钟。尽管ProGuard真的很慢,但是基本每个项目都会使用到它。加入了ProGuard之后,应用的构建过程流程如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b4/f3/b4bddb5e8cf4666b3f38f046998f33f3.png" alt="">
|
||||
|
||||
ProGuard主要有混淆、裁剪、优化这三大功能,它的整个处理流程是:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/03/e4/031014e00f8568c5eac9256782005ee4.png" alt="">
|
||||
|
||||
其中优化包括内联、修饰符、合并类和方法等30多种,具体介绍与使用方法你可以参考[官方文档](https://www.guardsquare.com/en/products/proguard/manual/usage/optimizations)。
|
||||
|
||||
**d8**
|
||||
|
||||
Android Studio 3.0推出了[d8](https://developer.android.com/studio/command-line/d8),并在3.1正式成为默认工具。它的作用是将“.class”文件编译为Dex文件,取代之前的dx工具。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/06/c3/0692ef05be5dac0803819274f6c6b3c3.png" alt="">
|
||||
|
||||
d8除了更快的编译速度之外,还有一个优化是减少生成的Dex大小。根据Google的测试结果,大约会有3%~5%的优化。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a4/ee/a410c50d547c49c37640632c81ec2dee.png" alt="">
|
||||
|
||||
**R8**
|
||||
|
||||
R8在Android Studio 3.1中引入,它的志向更加高远,它的目标是取代ProGuard和d8。我们可以直接使用R8把“.class”文件变成Dex。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9d/ac/9de1ce2e0d821dabcdabf82844601eac.png" alt="">
|
||||
|
||||
同时,R8还支持ProGuard中混淆、裁剪、优化这三大功能。由于目前R8依然处于实验阶段,网上的介绍资料并不多,你可以参考下面这些资料:
|
||||
|
||||
- ProGuard和R8对比:[ProGuard and R8: a comparison of optimizers](https://www.guardsquare.com/en/blog/proguard-and-r8)。
|
||||
- Jake Wharton大神的博客最近有很多R8相关的文章:[https://jakewharton.com/blog/](https://jakewharton.com/blog/)。
|
||||
|
||||
R8的最终目的跟d8一样,一个是加快编译速度,一个是更强大的代码优化。
|
||||
|
||||
**ReDex**
|
||||
|
||||
如果说R8是未来想取代的ProGuard的工具,那Facebook的内部使用的[ReDex](https://github.com/facebook/redex)其实已经做到了。
|
||||
|
||||
Facebook内部的很多项目都已经全部切换到ReDex,不再使用ProGuard了。跟ProGuard不同的是,它直接输入的对象是Dex,而不是“.class”文件,也就是它是直接针对最终产物的优化,所见即所得。
|
||||
|
||||
在前面的文章中,我已经不止一次提到ReDex这个项目,因为它里面的功能实在是太强大了,具体可以参考专栏前面的文章[《包体积优化(上):如何减少安装包大小?》](https://time.geekbang.org/column/article/81202)。
|
||||
|
||||
- Interdex:类重排和文件重排、Dex分包优化。
|
||||
- Oatmeal:直接生成的Odex文件。
|
||||
- StripDebugInfo:去除Dex中的Debug信息。
|
||||
|
||||
此外,ReDex中例如[Type Erasure](https://github.com/facebook/redex/tree/master/opt/type-erasure)和去除代码中的[Aceess方法](https://github.com/facebook/redex/tree/master/opt/access-marking)也是非常不错的功能,它们无论对包体积还是应用的运行速度都有帮助,因此我也鼓励你去研究和实践一下它们的用法和效果。但是ReDex的文档也是万年不更新的,而且里面掺杂了一些Facebook内部定制的逻辑,所以它用起来的确非常不方便。目前我主要还是直接研究它的源码,参考它的原理,然后再直接单独实现。
|
||||
|
||||
事实上,Buck里面其实也还有很多好用的东西,但是文档里面依然什么都没有提到,所以还是需要“read the source code”。
|
||||
|
||||
- Library Merge和Relinker
|
||||
- 多语言拆分
|
||||
- 分包支持
|
||||
- ReDex支持
|
||||
|
||||
## 持续交付
|
||||
|
||||
Gradle、Buck、Bazel它们代表的都是狭义上的编译,我认为广义的编译应该包括打包构建、Code Review、代码工程管理、代码扫描等流程,也就是业界最近经常提起的持续集成。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/a9/020fc61a0096102fa1b05be2f30b02a9.png" alt="">
|
||||
|
||||
目前最常用的持续集成工具有Jenkins、GitLab CI、Travis CI等,GitHub也有提供自己的持续集成服务。每个大公司都有自己的持续集成方案,例如腾讯的RDM、阿里的摩天轮、大众点评的[MCI](https://tech.meituan.com/2018/07/12/mci.html)等。
|
||||
|
||||
下面我来简单讲一下我对持续集成的一些经验和看法:
|
||||
|
||||
<li>
|
||||
**自定义代码检查**。每个公司都会有自己的编码规范,代码检查的目的在于防止不符合规范的代码提交到远程仓库中。比如微信就定义了一套代码规范,并且写了专门的插件来检测。例如日志规范、不能直接使用new Thread、new Handler等,而且违反者将会得到一定的惩罚。自定义代码检测可以通过完全自己实现或者扩展Findbugs插件,例如美团它们就利用Findbugs实现了[Android漏洞扫描工具Code Arbiter](https://tech.meituan.com/2017/08/17/android-code-arbiter.html)。
|
||||
</li>
|
||||
<li>
|
||||
**第三方代码检查**。业界比较常用的代码扫描工具有收费的Coverity,以及Facebook开源的[Infer](https://github.com/facebook/infer),例如空指针、多线程问题、资源泄漏等很多问题都可以扫描出来。除了增加检测流程,我最大的体会是需要同时增加人员的培训。我遇到很多开发者为了解决扫描出来的问题,空指针就直接判空、多线程就直接加锁,最后可能会造成更加严重的问题。
|
||||
</li>
|
||||
<li>
|
||||
**Code Review**。关于Code Review,集成GitLab、Phabricator或者Gerrit都是不错的选择。我们一定要重视Code Review,这也是给其他人展示我们“伟大”代码的机会。而且我们自己应该是第一个Code Reviewer,在给别人Review之前,自己先以第三者的角度审视一次代码。这样先通过自己这一关的考验,既尊重了别人的时间,也可以为自己树立良好的技术品牌。
|
||||
</li>
|
||||
|
||||
持续集成涉及的流程有很多,你需要结合自己团队的现状。如果只是一味地去增加流程,有时候可能适得其反。
|
||||
|
||||
## 总结
|
||||
|
||||
在Android 8.0,Google引入了[Dexlayout](https://source.android.com/devices/tech/dalvik/improvements)库实现类和方法的重排,Facebook的Buck也第一时间引入了AAPT2。ReDex、d8、R8其实都是相辅相成,可以看到Google也在摄取社区的知识,但同时我们也会从Google的新技术发展里寻求思路。
|
||||
|
||||
我在写今天的内容时还有另外一个体会,Google为了解决Android编译速度的问题,花了大量的力气结果却不尽如人意。我想说如果我们敢于跳出系统的制约,可能才会彻底解决这个问题,正如在Flutter上面就可以完美实现秒级编译。其实做人、做事也是如此,我们经常会陷入局部最优解的困局,或者走进“思维怪圈”,这时如果能跳出路径依赖,从更高的维度重新思考、审视全局,得到的体会可能会完全不一样。
|
||||
|
||||
## 课后作业
|
||||
|
||||
在你的工作中,遇到过哪些编译问题?有没有做过具体优化编译速度的工作?对于编译,你还有哪些疑问?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
对于Android Build System,可以说每年都会有不少的变化,也有很多新的东西出来。所以我们应该保持敏感度,你会发现很多工具都非常有用,例如Desugar、Dexlayout、JVM TI、App Bundle等。
|
||||
|
||||
今天的课后作业是,请你观看2018年Google I/O编译工具相关的视频,在留言中写下自己的心得体会。
|
||||
|
||||
<li>
|
||||
[What’s new with the Android build system (Google I/O '18)](http://v.youku.com/v_show/id_XMzYwMDQ3MDk2OA==.html?spm=a2h0k.11417342.soresults.dtitle)
|
||||
</li>
|
||||
<li>
|
||||
[What’s new in Android development tools](http://v.youku.com/v_show/id_XMzU5ODExNzQzMg==.html?spm=a2h0k.11417342.soresults.dtitle)
|
||||
</li>
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
321
极客时间专栏/Android开发高手课/模块二 高效开发/27 | 编译插桩的三种方法:AspectJ、ASM、ReDex.md
Normal file
321
极客时间专栏/Android开发高手课/模块二 高效开发/27 | 编译插桩的三种方法:AspectJ、ASM、ReDex.md
Normal file
@@ -0,0 +1,321 @@
|
||||
<audio id="audio" title="27 | 编译插桩的三种方法:AspectJ、ASM、ReDex" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c7/c8/c7ecd38869e2f6226e91f032093522c8.mp3"></audio>
|
||||
|
||||
只要简单回顾一下前面课程的内容你就会发现,在启动耗时分析、网络监控、耗电监控中已经不止一次用到编译插桩的技术了。那什么是编译插桩呢?顾名思义,所谓的编译插桩就是在代码编译期间修改已有的代码或者生成新代码。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/be/75/be872dd6d12ab22879fbec9414fb2d75.png" alt="">
|
||||
|
||||
如上图所示,请你回忆一下Java代码的编译流程,思考一下插桩究竟是在编译流程中的哪一步工作?除了我们之前使用的一些场景,它还有哪些常见的应用场景?在实际工作中,我们应该怎样更好地使用它?现在都有哪些常用的编译插桩方法?今天我们一起来解决这些问题。
|
||||
|
||||
## 编译插桩的基础知识
|
||||
|
||||
不知道你有没有注意到,在编译期间修改和生成代码其实是很常见的行为,无论是Dagger、ButterKnife这些APT(Annotation Processing Tool)注解生成框架,还是新兴的Kotlin语言[编译器](https://github.com/JetBrains/kotlin/tree/v1.2.30/compiler/backend/src/org/jetbrains/kotlin/codegen),它们都用到了编译插桩的技术。
|
||||
|
||||
下面我们一起来看看还有哪些场景会用到编译插桩技术。
|
||||
|
||||
**1. 编译插桩的应用场景**
|
||||
|
||||
编译插桩技术非常有趣,同样也很有价值,掌握它之后,可以完成一些其他技术很难实现或无法完成的任务。学会这项技术以后,我们就可以随心所欲地操控代码,满足不同场景的需求。
|
||||
|
||||
<li>
|
||||
**代码生成**。除了Dagger、ButterKnife这些常用的注解生成框架,Protocol Buffers、数据库ORM框架也都会在编译过程生成代码。代码生成隔离了复杂的内部实现,让开发更加简单高效,而且也减少了手工重复的劳动量,降低了出错的可能性。
|
||||
</li>
|
||||
<li>
|
||||
**代码监控**。除了网络监控和耗电监控,我们可以利用编译插桩技术实现各种各样的性能监控。为什么不直接在源码中实现监控功能呢?首先我们不一定有第三方SDK的源码,其次某些调用点可能会非常分散,例如想监控代码中所有new Thread()调用,通过源码的方式并不那么容易实现。
|
||||
</li>
|
||||
<li>
|
||||
**代码修改**。我们在这个场景拥有无限的发挥空间,例如某些第三方SDK库没有源码,我们可以给它内部的一个崩溃函数增加try catch,或者说替换它的图片库等。我们也可以通过代码修改实现无痕埋点,就像网易的[HubbleData](https://neyoufan.github.io/2017/07/11/android/%E7%BD%91%E6%98%93HubbleData%E4%B9%8BAndroid%E6%97%A0%E5%9F%8B%E7%82%B9%E5%AE%9E%E8%B7%B5/)、51信用卡的[埋点实践](https://mp.weixin.qq.com/s/P95ATtgT2pgx4bSLCAzi3Q)。
|
||||
</li>
|
||||
<li>
|
||||
**代码分析**。上一期我讲到持续集成,里面的自定义代码检查就可以使用编译插桩技术实现。例如检查代码中的new Thread()调用、检查代码中的一些敏感权限使用等。事实上,Findbugs这些第三方的代码检查工具也同样使用的是编译插桩技术实现。
|
||||
</li>
|
||||
|
||||
“一千个人眼中有一千个哈姆雷特”,通过编译插桩技术,你可以大胆发挥自己的想象力,做一些对提升团队质量和效能有帮助的事情。
|
||||
|
||||
那从技术实现上看,编译插桩是从代码编译的哪个流程介入的呢?我们可以把它分为两类:
|
||||
|
||||
- **Java文件**。类似APT、AndroidAnnotation这些代码生成的场景,它们生成的都是Java文件,是在编译的最开始介入。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/76/c7f5fb2696906585f13f89eb3ac42c76.png" alt="">
|
||||
|
||||
- **字节码(Bytecode)**。对于代码监控、代码修改以及代码分析这三个场景,一般采用操作字节码的方式。可以操作“.class”的Java字节码,也可以操作“.dex”的Dalvik字节码,这取决于我们使用的插桩方法。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c1/db/c1208262168a3686bd5d48c1080b6edb.png" alt="">
|
||||
|
||||
相对于Java文件方式,字节码操作方式功能更加强大,应用场景也更广,但是它的使用复杂度更高,所以今天我主要来讲如何通过操作字节码实现编译插桩的功能。
|
||||
|
||||
**2. 字节码**
|
||||
|
||||
对于Java平台,Java虚拟机运行的是Class文件,内部对应的是Java字节码。而针对Android这种嵌入式平台,为了优化性能,Android虚拟机运行的是[Dex文件](https://source.android.com/devices/tech/dalvik/dex-format),Google专门为其设计了一种Dalvik字节码,虽然增加了指令长度但却缩减了指令的数量,执行也更为快速。
|
||||
|
||||
那这两种字节码格式有什么不同呢?下面我们先来看一个非常简单的Java类。
|
||||
|
||||
```
|
||||
public class Sample {
|
||||
public void test() {
|
||||
System.out.print("I am a test sample!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
通过下面几个命令,我们可以生成和查看这个Sample.java类的Java字节码和Dalvik字节码。
|
||||
|
||||
```
|
||||
javac Sample.java // 生成Sample.class,也就是Java字节码
|
||||
javap -v Sample // 查看Sample类的Java字节码
|
||||
|
||||
//通过Java字节码,生成Dalvik字节码
|
||||
dx --dex --output=Sample.dex Sample.class
|
||||
|
||||
dexdump -d Sample.dex // 查看Sample.dex的Dalvik的字节码
|
||||
|
||||
```
|
||||
|
||||
你可以直观地看到Java字节码和Dalvik字节码的差别。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8c/50/8c9a2f2a6bfe9e2d42b9f1ea3a642250.png" alt="">
|
||||
|
||||
它们的格式和指令都有很明显的差异。关于Java字节码的介绍,你可以参考[JVM文档](https://docs.oracle.com/javase/specs/jvms/se11/html/index.html)。对于Dalvik字节码来说,你可以参考Android的[官方文档](https://source.android.com/devices/tech/dalvik/dalvik-bytecode)。它们的主要区别有:
|
||||
|
||||
- **体系结构**。Java虚拟机是基于栈实现,而Android虚拟机是基于寄存器实现。在ARM平台,寄存器实现性能会高于栈实现。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/33/1c/3357723f90b34af2717e320b61dca01c.png" alt="">
|
||||
|
||||
<li>
|
||||
**格式结构**。对于Class文件,每个文件都会有自己单独的常量池以及其他一些公共字段。对于Dex文件,整个Dex中的所有Class共用同一个常量池和公共字段,所以整体结构更加紧凑,因此也大大减少了体积。
|
||||
</li>
|
||||
<li>
|
||||
**指令优化**。Dalvik字节码对大量的指令专门做了精简和优化,如下图所示,相同的代码Java字节码需要100多条,而Dalvik字节码只需要几条。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/35/4f/359817cf20201f045eef60a31f45ff4f.png" alt="">
|
||||
|
||||
关于Java字节码和Dalvik字节码的更多介绍,你可以参考下面的资料:
|
||||
|
||||
<li>
|
||||
[Dalvik and ART](https://github.com/AndroidAdvanceWithGeektime/Chapter27/blob/master/doucments/Dalvik%20and%20ART.pdf)
|
||||
</li>
|
||||
<li>
|
||||
[Understanding the Davlik Virtual Machine](https://github.com/AndroidAdvanceWithGeektime/Chapter27/blob/master/doucments/Understanding%20the%20Davlik%20Virtual%20Machine.pdf)
|
||||
</li>
|
||||
|
||||
## 编译插桩的三种方法
|
||||
|
||||
AspectJ和ASM框架的输入和输出都是Class文件,它们是我们最常用的Java字节码处理框架。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7d/2c/7d428fcc675b9308d4059a855295692c.png" alt="">
|
||||
|
||||
**1. AspectJ**
|
||||
|
||||
[AspectJ](https://www.eclipse.org/aspectj/)是Java中流行的AOP(aspect-oriented programming)编程扩展框架,网上很多文章说它处理的是Java文件,其实并不正确,它内部也是通过字节码处理技术实现的代码注入。
|
||||
|
||||
从底层实现上来看,AspectJ内部使用的是[BCEL](https://github.com/apache/commons-bcel)框架来完成的,不过这个库在最近几年没有更多的开发进展,官方也建议切换到ObjectWeb的ASM框架。关于BCEL的使用,你可以参考[《用BCEL设计字节码》](https://www.ibm.com/developerworks/cn/java/j-dyn0414/index.html)这篇文章。
|
||||
|
||||
从使用上来看,作为字节码处理元老,AspectJ的框架的确有自己的一些优势。
|
||||
|
||||
<li>
|
||||
**成熟稳定**。从字节码的格式和各种指令规则来看,字节码处理不是那么简单,如果处理出错,就会导致程序编译或者运行过程出问题。而AspectJ作为从2001年发展至今的框架,它已经很成熟,一般不用考虑插入的字节码正确性的问题。
|
||||
</li>
|
||||
<li>
|
||||
**使用简单**。AspectJ功能强大而且使用非常简单,使用者完全不需要理解任何Java字节码相关的知识,就可以使用自如。它可以在方法(包括构造方法)被调用的位置、在方法体(包括构造方法)的内部、在读写变量的位置、在静态代码块内部、在异常处理的位置等前后,插入自定义的代码,或者直接将原位置的代码替换为自定义的代码。
|
||||
</li>
|
||||
|
||||
在专栏前面文章里我提过360的性能监控框架[ArgusAPM](https://github.com/Qihoo360/ArgusAPM),它就是使用AspectJ实现性能的监控,其中[TraceActivity](https://github.com/Qihoo360/ArgusAPM/blob/master/argus-apm/argus-apm-aop/src/main/java/com/argusapm/android/aop/TraceActivity.java)是为了监控Application和Activity的生命周期。
|
||||
|
||||
```
|
||||
// 在Application onCreate执行的时候调用applicationOnCreate方法
|
||||
@Pointcut("execution(* android.app.Application.onCreate(android.content.Context)) && args(context)")
|
||||
public void applicationOnCreate(Context context) {
|
||||
|
||||
}
|
||||
// 在调用applicationOnCreate方法之后调用applicationOnCreateAdvice方法
|
||||
@After("applicationOnCreate(context)")
|
||||
public void applicationOnCreateAdvice(Context context) {
|
||||
AH.applicationOnCreate(context);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
你可以看到,我们完全不需要关心底层Java字节码的处理流程,就可以轻松实现编译插桩功能。关于AspectJ的文章网上有很多,不过最全面的还是官方文档,你可以参考[《AspectJ程序设计指南》](https://github.com/AndroidAdvanceWithGeektime/Chapter27/blob/master/AspectJ%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E6%8C%87%E5%8D%97.pdf)和[The AspectJ 5 Development Kit Developer’s Notebook](https://www.eclipse.org/aspectj/doc/next/adk15notebook/index.html),这里我就不详细描述了。
|
||||
|
||||
但是从AspectJ的使用说明里也可以看出它的一些劣势,它的功能无法满足我们某些场景的需要。
|
||||
|
||||
<li>
|
||||
**切入点固定**。AspectJ只能在一些固定的切入点来进行操作,如果想要进行更细致的操作则无法完成,它不能针对一些特定规则的字节码序列做操作。
|
||||
</li>
|
||||
<li>
|
||||
**正则表达式**。AspectJ的匹配规则是类似正则表达式的规则,比如匹配Activity生命周期的onXXX方法,如果有自定义的其他以on开头的方法也会匹配到。
|
||||
</li>
|
||||
<li>
|
||||
**性能较低**。AspectJ在实现时会包装自己的一些类,逻辑比较复杂,不仅生成的字节码比较大,而且对原函数的性能也会有所影响。
|
||||
</li>
|
||||
|
||||
我举专栏第7期启动耗时[Sample](https://github.com/AndroidAdvanceWithGeektime/Chapter07)的例子,我们希望在所有的方法调用前后都增加Trace的函数。如果选择使用AspectJ,那么实现真的非常简单。
|
||||
|
||||
```
|
||||
@Before("execution(* **(..))")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
Trace.beginSection(joinPoint.getSignature().toString());
|
||||
}
|
||||
|
||||
@After("execution(* **(..))")
|
||||
public void after() {
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
但你可以看到经过AspectJ的字节码处理,它并不会直接把Trace函数直接插入到代码中,而是经过一系列自己的封装。如果想针对所有的函数都做插桩,AspectJ会带来不少的性能影响。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/9b/3cc968bb9654fbabbcc26d212ad7719b.png" alt="">
|
||||
|
||||
不过大部分情况,我们可能只会插桩某一小部分函数,这样AspectJ带来的性能影响就可以忽略不计了。如果想在Android中直接使用AspectJ,还是比较麻烦的。这里我推荐你直接使用沪江的[AspectJX](https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx)框架,它不仅使用更加简便一些,而且还扩展了排除某些类和JAR包的能力。如果你想通过Annotation注解方式接入,我推荐使用Jake Wharton大神写的[Hugo](https://github.com/JakeWharton/hugo)项目。
|
||||
|
||||
虽然AspectJ使用方便,但是在使用的时候不注意的话还是会产生一些意想不到的异常。比如使用Around Advice需要注意方法返回值的问题,在Hugo里的处理方法是将joinPoint.proceed()的返回值直接返回,同时也需要注意[Advice Precedence](https://www.eclipse.org/aspectj/doc/released/progguide/semantics-advice.html)的情况。
|
||||
|
||||
**2. ASM**
|
||||
|
||||
如果说AspectJ只能满足50%的字节码处理场景,那[ASM](http://asm.ow2.org/)就是一个可以实现100%场景的Java字节码操作框架,它的功能也非常强大。使用ASM操作字节码主要的特点有:
|
||||
|
||||
<li>
|
||||
**操作灵活**。操作起来很灵活,可以根据需求自定义修改、插入、删除。
|
||||
</li>
|
||||
<li>
|
||||
**上手难**。上手比较难,需要对Java字节码有比较深入的了解。
|
||||
</li>
|
||||
|
||||
为了使用简单,相比于BCEL框架,ASM的优势是提供了一个Visitor模式的访问接口(Core API),使用者可以不用关心字节码的格式,只需要在每个Visitor的位置关心自己所修改的结构即可。但是这种模式的缺点是,一般只能在一些简单场景里实现字节码的处理。
|
||||
|
||||
事实上,专栏第7期启动耗时的Sample内部就是使用ASM的Core API,具体你可以参考[MethodTracer](https://github.com/AndroidAdvanceWithGeektime/Chapter07/blob/master/systrace-gradle-plugin/src/main/java/com/geektime/systrace/MethodTracer.java#L259)类的实现。从最终效果上来看,ASM字节码处理后的效果如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/51/2d/51f13d88cebf089fee2e90154206852d.png" alt="">
|
||||
|
||||
相比AspectJ,ASM更加直接高效。但是对于一些复杂情况,我们可能需要使用另外一种Tree API来完成对Class文件更直接的修改,因此这时候你要掌握一些必不可少的Java字节码知识。
|
||||
|
||||
此外,我们还需要对Java虚拟机的运行机制有所了解,前面我就讲到Java虚拟机是基于栈实现。那什么是Java虚拟机的栈呢?,引用《Java虚拟机规范》里对Java虚拟机栈的描述:
|
||||
|
||||
>
|
||||
每一条Java虚拟机线程都有自己私有的Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧(Stack Frame)。
|
||||
|
||||
|
||||
正如这句话所描述的,每个线程都有自己的栈,所以在多线程应用程序中多个线程就会有多个栈,每个栈都有自己的栈帧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/49/9a/49f6179e747be98e6b36119d3151839a.png" alt="">
|
||||
|
||||
如下图所示,我们可以简单认为栈帧包含3个重要的内容:本地变量表(Local Variable Array)、操作数栈(Operand Stack)和常量池引用(Constant Pool Reference)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/42/00/426fb987bed676f448ebf8e938df2a00.png" alt="">
|
||||
|
||||
<li>
|
||||
**本地变量表**。在使用过程中,可以认为本地变量表是存放临时数据的,并且本地变量表有个很重要的功能就是用来传递方法调用时的参数,当调用方法的时候,参数会依次传递到本地变量表中从0开始的位置上,并且如果调用的方法是实例方法,那么我们可以通过第0个本地变量中获取当前实例的引用,也就是this所指向的对象。
|
||||
</li>
|
||||
<li>
|
||||
**操作数栈**。可以认为操作数栈是一个用于存放指令执行所需要的数据的位置,指令从操作数栈中取走数据并将操作结果重新入栈。
|
||||
</li>
|
||||
|
||||
由于本地变量表的最大数和操作数栈的最大深度是在编译时就确定的,所以在使用ASM进行字节码操作后需要调用ASM提供的visitMaxs方法来设置maxLocal和maxStack数。不过,ASM为了方便用户使用,已经提供了自动计算的方法,在实例化ClassWriter操作类的时候传入COMPUTE_MAXS后,ASM就会自动计算本地变量表和操作数栈。
|
||||
|
||||
```
|
||||
ClassWriter(ClassWriter.COMPUTE_MAXS)
|
||||
|
||||
```
|
||||
|
||||
下面以一个简单的“1+2“为例,它的操作数以LIFO(后进先出)的方式进行操作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/89/eb/89dd2ea472be03aff9bfddba5b5ff3eb.png" alt="">
|
||||
|
||||
ICONST_1将int类型1推送栈顶,ICONST_2将int类型2推送栈顶,IADD指令将栈顶两个int类型的值相加后将结果推送至栈顶。操作数栈的最大深度也是由编译期决定的,很多时候ASM修改后的代码会增加操作数栈最大深度。不过ASM已经提供了动态计算的方法,但同时也会带来一些性能上的损耗。
|
||||
|
||||
在具体的字节码处理过程中,特别需要注意的是本地变量表和操作数栈的数据交换和try catch blcok的处理。
|
||||
|
||||
- **数据交换**。如下图所示,在经过IADD指令操作后,又通过ISTORE 0指令将栈顶int值存入第1个本地变量中,用于临时保存,在最后的加法过程中,将0和1位置的本地变量取出压入操作数栈中供IADD指令使用。关于本地变量和操作数栈数据交互的指令,你可以参考虚拟机规范,里面提供了一系列根据不同数据类型的指令。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f6/e2/f6db02fcc40462ad054f37ff36bcf1e2.png" alt="">
|
||||
|
||||
- **异常处理**。在字节码操作过程中需要特别注意异常处理对操作数栈的影响,如果在try和catch之间抛出了一个可捕获的异常,那么当前的操作数栈会被清空,并将异常对象压入这个空栈中,执行过程在catch处继续。幸运的是,如果生成了错误的字节码,编译器可以辨别出该情况并导致编译异常,ASM中也提供了[字节码Verify](https://asm.ow2.io/javadoc/org/objectweb/asm/util/CheckClassAdapter.html)的接口,可以在修改完成后验证一下字节码是否正常。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ac/f5/ac268beb15b325d380097f64eb1eabf5.png" alt="">
|
||||
|
||||
如果想在一个方法执行完成后增加代码,ASM相对也要简单很多,可以在字节码中出现的每一条RETURN系或者ATHROW的指令前,增加处理的逻辑即可。
|
||||
|
||||
**3. ReDex**
|
||||
|
||||
ReDex不仅只是作为一款Dex优化工具,它也提供了很多的小工具和文档里没有提到的一些新奇功能。比如在ReDex里提供了一个简单的Method Tracing和Block Tracing工具,这个工具可以在所有方法或者指定方法前面插入一段跟踪代码。
|
||||
|
||||
官方提供了一个例子,用来展示这个工具的使用,具体请查看[InstrumentTest](https://github.com/facebook/redex/blob/5d0d4f429198a56c83c013b26b1093d80edc842b/test/instr/InstrumentTest.config)。这个例子会将[InstrumentAnalysis](https://github.com/facebook/redex/blob/5d0d4f429198a56c83c013b26b1093d80edc842b/test/instr/InstrumentAnalysis.java)的onMethodBegin方法插入到除黑名单以外的所有方法的开头位置。具体配置如下:
|
||||
|
||||
```
|
||||
"InstrumentPass" : {
|
||||
"analysis_class_name": "Lcom/facebook/redextest/InstrumentAnalysis;", //存在桩代码的类
|
||||
"analysis_method_name": "onMethodBegin", //存在桩代码的方法
|
||||
"instrumentation_strategy": "simple_method_tracing"
|
||||
, //插入策略,有两种方案,一种是在方法前面插入simple_method_tracing,一种是在CFG 的Block前后插入basic_block_tracing
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
ReDex的这个功能并不是完整的AOP工具,但它提供了一系列指令生成API和Opcode插入API,我们可以参照这个功能实现自己的字节码注入工具,这个功能的代码在[Instrument.cpp](https://github.com/facebook/redex/blob/master/opt/instrument/Instrument.cpp)中。
|
||||
|
||||
这个类已经将各种字节码特殊情况处理得相对比较完善,我们可以直接构造一段Opcode调用其提供的Insert接口即可完成代码的插入,而不用过多考虑可能会出现的异常情况。不过这个类提供的功能依然耦合了ReDex的业务,所以我们需要提取有用的代码加以使用。
|
||||
|
||||
由于Dalvik字节码发展时间尚短,而且因为Dex格式更加紧凑,修改起来往往牵一发而动全身。并且Dalvik字节码的处理相比Java字节码会更加复杂一些,所以直接操作Dalvik字节码的工具并不是很多。
|
||||
|
||||
市面上大部分需要直接修改Dex的情况是做逆向,很多同学都采用手动书写Smali代码然后编译回去。这里我总结了一些修改Dalvik字节码的库。
|
||||
|
||||
<li>
|
||||
[ASMDEX](https://gitlab.ow2.org/asm/asmdex),开发者是ASM库的开发者,但很久未更新了。
|
||||
</li>
|
||||
<li>
|
||||
[Dexter](https://android.googlesource.com/platform/tools/dexter/+/refs/heads/master),Google官方开发的Dex操作库,更新很频繁,但使用起来很复杂。
|
||||
</li>
|
||||
<li>
|
||||
[Dexmaker](https://github.com/linkedin/dexmaker),用来生成Dalvik字节码的代码。
|
||||
</li>
|
||||
<li>
|
||||
[Soot](https://github.com/Sable/soot),修改Dex的方法很另类,是先将Dalvik字节码转成一种Jimple three-address code,然后插入Jimple Opcode后再转回Dalvik字节码,具体可以参考[例子](https://raw.githubusercontent.com/wiki/Sable/soot/code/androidinstr/AndroidInstrument.java_.txt)。
|
||||
</li>
|
||||
|
||||
## 总结
|
||||
|
||||
今天我介绍了几种比较有代表性的框架来讲解编译插桩相关的内容。代码生成、代码监控、代码魔改以及代码分析,编译插桩技术无所不能,因此需要我们充分发挥想象力。
|
||||
|
||||
对于一些常见的应用场景,前辈们付出了大量的努力将它们工具化、API化,让我们不需要懂得底层字节码原理就可以轻松使用。但是如果真要想达到随心所欲的境界,即使有类似ASM工具的帮助,也还是需要我们对底层字节码有比较深的理解和认识。
|
||||
|
||||
当然你也可以成为“前辈”,将这些场景沉淀下来,提供给后人使用。但有的时候“能力限制想象力”,如果能力不够,即使想象力到位也无可奈何。
|
||||
|
||||
## 课后作业
|
||||
|
||||
你使用过哪些编译插桩相关的工具?使用编译插桩实现过什么功能?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
今天的课后作业是重温专栏[第7期练习Sample](https://github.com/AndroidAdvanceWithGeektime/Chapter07)的实现原理,看看它内部是如何使用ASM完成TAG的插桩。在今天的[Sample](https://github.com/AndroidAdvanceWithGeektime/Chapter27)里,我也提供了一个使用AspectJ实现的版本。想要彻底学会编译插桩的确不容易,单单写一个高效的Gradle Plugin就不那么简单。
|
||||
|
||||
除了上面的两个Sample,我也推荐你认真看看下面的一些参考资料和项目。
|
||||
|
||||
<li>
|
||||
[一起玩转Android项目中的字节码](https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650244795&idx=1&sn=cdfc4acec8b0d2b5c82fd9d884f32f09&chksm=886377d4bf14fec2fc822cd2b3b6069c36cb49ea2814d9e0e2f4a6713f4e86dfc0b1bebf4d39&mpshare=1&scene=1&srcid=1217NjDpKNvdgalsqBQLJXjX%23rd)
|
||||
</li>
|
||||
<li>
|
||||
[字节码操纵技术探秘](https://www.infoq.cn/article/Living-Matrix-Bytecode-Manipulation)
|
||||
</li>
|
||||
<li>
|
||||
[ASM 6 Developer Guide](https://asm.ow2.io/developer-guide.html)
|
||||
</li>
|
||||
<li>
|
||||
[Java字节码(Bytecode)与ASM简单说明](http://blog.hakugyokurou.net/?p=409)
|
||||
</li>
|
||||
<li>
|
||||
[Dalvik and ART](https://github.com/AndroidAdvanceWithGeektime/Chapter27/blob/master/doucments/Dalvik%20and%20ART.pdf)
|
||||
</li>
|
||||
<li>
|
||||
[Understanding the Davlik Virtual Machine](https://github.com/AndroidAdvanceWithGeektime/Chapter27/blob/master/doucments/Understanding%20the%20Davlik%20Virtual%20Machine.pdf)
|
||||
</li>
|
||||
<li>
|
||||
基于ASM的字节码处理工具:[Hunter](https://github.com/Leaking/Hunter/blob/master/README_ch.md)和[Hibeaver](https://github.com/BryanSharp/hibeaver)
|
||||
</li>
|
||||
<li>
|
||||
基于Javassist的字节码处理工具:[DroidAssist](https://github.com/didi/DroidAssist/blob/master/README_CN.md)
|
||||
</li>
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
181
极客时间专栏/Android开发高手课/模块二 高效开发/28 | 大数据与AI,如何高效地测试?.md
Normal file
181
极客时间专栏/Android开发高手课/模块二 高效开发/28 | 大数据与AI,如何高效地测试?.md
Normal file
@@ -0,0 +1,181 @@
|
||||
<audio id="audio" title="28 | 大数据与AI,如何高效地测试?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ab/f8/ab94103f51a84c84aef815957fcfdff8.mp3"></audio>
|
||||
|
||||
测试作为持续交付中重要的一个环节,它的使命是发现交付过程的质量问题。随着互联网迭代速度的加快,很多产品都是两周甚至每周一个版本,留给测试的时间越来越少。
|
||||
|
||||
那在这么短的时间,如何保障产品的质量,怎样高效地测试呢?我们研发模式在不断地变化,测试的定位又有哪些改变,而未来的测试又会发展成什么样的形态呢?
|
||||
|
||||
## 测试的演进历程
|
||||
|
||||
“专业的事情留给专业的人员”,社会各领域的分工越来越细,设计、研发、产品、测试大家各司其职,共同完成一个产品。但随着技术的发展,这样的分工并不是一成不变,从近两年大公司技术部门的组织调整来看,测试和开发的角色已经在不断融合。
|
||||
|
||||
互联网发展到今天,测试的职责发生了哪些改变?移动端测试又经历了怎么样的演进历程呢?
|
||||
|
||||
**1. 测试的田园时代**
|
||||
|
||||
在移动互联网起步之初,基本上还处在传统的软件研发阶段。产品将需求交给研发,研发实现后交给测试,测试把最终产品交付给用户。我们可以把这个阶段叫作测试的“田园时代“。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/9e/f1fbb65d6f8b77c9c7f7efafe63ca09e.png" alt="">
|
||||
|
||||
测试作为研发流程的一个环节,只是作为产品交付给用户前的一道屏障,承担着质量保证工作。在这个阶段测试有两个最重要的考核指标:每个版本发现的Bug数量和遗漏到线上的Bug数量。
|
||||
|
||||
在测试的“田园时代”,很多Bug可能会出现多次返工,产品的交付流程也很难快起来。这个阶段的主要问题有:
|
||||
|
||||
<li>
|
||||
**人员对立**。测试的KPI是尽可能地发现开发人员的Bug,他们之间非常容易发生对立。特别是出现线上事故的时候,开发埋怨测试没用,测试埋怨开发无能。
|
||||
</li>
|
||||
<li>
|
||||
**效率低下**。虽然引入了Monkey这些基本的自动化工具,但是大多数的测试方法还是依赖人工执行。尽管我们不停地加大测试与研发人员的比例,但整个过程还是问题多多,很难缩短整个产品的交付周期。
|
||||
</li>
|
||||
|
||||
**2. 测试的效能时代**
|
||||
|
||||
随着互联网竞争的加剧,交付速度成为了产品的核心能力。国内外的大公司开始适应趋势的变化,把测试团队的职责转变成为团队的效能服务。
|
||||
|
||||
测试不只是负责交付质量,还需要同时思考产品的质量和效率。正如我前面说到的“211”效能目标,也就是2周交付周期、1周开发周期以及1小时发布时长,测试人员需要直接为这个目标负责,思考如何快速并且高质量完成产品的交付。
|
||||
|
||||
>
|
||||
长兄于病视神,未有形而除之,故名不出于家。中兄治病,其在毫毛,故名不出于闾。若扁鹊者,镵血脉,投毒药,副肌肤,闲而名出闻于诸侯。
|
||||
|
||||
|
||||
这是扁鹊讲三兄弟治病的故事,说的是长兄治病,是治于病情未发作之前,由于一般人不知道他事先能铲除病因,所以他的名气无法传出去。中兄治病,是治于病情初起之时,一般人以为他只能治轻微的小病,所以他的名气只在乡里。而扁鹊是治于病情严重之时,在经脉上穿针管来放血,在皮肤上敷药,所以都以为我的医术最高明,名气因此响遍天下。
|
||||
|
||||
从这个故事来看,扁鹊认为长兄的医术最高,可以做到“治病于未发”。回到我们今天所谈的测试,很多公司已经开始提出“测试左移”,也是希望测试在更早期的阶段介入到交付过程中,不仅是发现问题,要考虑更多的是如何避免问题的出现。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dd/89/ddcab1470783d34d3dbc4d331938a089.png" alt="">
|
||||
|
||||
测试需要深入到产品从设计到发布的各个流程,要做到“**比产品更懂技术,比研发更懂业务**”。为了顺应这个变化,各大公司在组织架构上开始把大型的测试团队打散,揉碎进各个业务开发团队中。“产研测一体化”目的在于统一团队的目标和方向,让所有人为了提升产品效能这个共同目标而努力。这样团队中所有成员成为相亲相爱的一家人,也消除了“产研测”之间的对立现象。
|
||||
|
||||
但是在[《如何衡量研发效能》](https://mp.weixin.qq.com/s/vfhqRxLnHJz_ii2zhXofuA)一文中所提到的:“在产品迭代前期,团队集中设计、编码,引入缺陷,但并未即时地集成和验证。缺陷一直掩藏在系统中,直到项目后期,团队才开始集成和测试,缺陷集中爆发”。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f6/35/f657c510865c4718640ddd5314651a35.png" alt="">
|
||||
|
||||
虽然通过持续交付模式,可以一定程度上削减提交波峰,但是依然无法避免经常出现的“踩点”提交。在测试的效能时代,如何提升测试的效率依然是急需解决的问题。
|
||||
|
||||
在这个阶段,我给出的答案是持续集成的工具化和平台化。从需求发起到分支管理、Code Review、代码检查以及测试发布等,测试团队负责把控各式各样的工具或平台。
|
||||
|
||||
由于整个持续交付过程涉及各个阶段的平台工具,这里我只挑跟测试相关的两个平台重点来讲。
|
||||
|
||||
- **测试平台**。竞品测试、弱网络测试、启动测试、UI测试等,整个测试流程引入了大量的自动化工具。各大公司也改良或创造了不少好用的工具,例如腾讯的New Monkey工具,可以大大提升Monkey的智能度和覆盖率。除了我们比较熟悉的[Espresso](https://developer.android.com/training/testing/espresso/index.html)、[UIAutomator](https://developer.android.com/training/testing/ui-automator)、[Appium](http://appium.io/)以及[Robotium](https://github.com/RobotiumTech/robotium)自动化框架,像阿里的[Macaca](https://github.com/macacajs/macaca-android)、Facebook的[Screenshot Tests for Android](https://github.com/facebook/screenshot-tests-for-android)都是值得学习的开源测试框架。当然,也有一些公司把测试平台打包成服务对外公开,例如腾讯的[WeTest](https://wetest.qq.com/)、华为的[开发者服务](https://developer.huawei.com/consumer/cn/)等。
|
||||
|
||||
下面是这些常用工具的简单对比,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/21/fa/21890d63ffedd1ea983f30ec397905fa.png" alt="">
|
||||
|
||||
- **体验平台**。在测试的效能时代,自动化并不能完全取代人工测试。在产品交付的不同阶段,我们需要提高团队内外人员的参与度。无论每日的Daily Build、封版时的集中体验,还是测试期间的员工内部体验、灰度期间的外部众测平台,的目的是让尽量多的成员都参与到产品的体验中,提升团队成员对产品的认同感。当然各大公司也都有自己的体验平台,例如腾讯的RDM、蚂蚁的伙伴APP等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/f5/5e427f02ed7bef0c27c7e2a58a8961f5.png" alt="">
|
||||
|
||||
测试的效能时代也是目前大多数公司所处的阶段,据我了解很多公司的工程效能团队,也是从测试团队演进而来的。随着测试团队职能的转变以及技术深度的提升,会涌现出一大批资深的测试开发人员,也会有更多优秀的测试人员走向开发或者产品的岗位。
|
||||
|
||||
不过我发现关于测试国内外也有一些差异,例如国外十分推崇开发编写的test case,但在国内却非常不容易推行。这主要是因为国内业务的迭代更加快速,开发需求都做不完,根本没有时间去写test case,更不用说有的test case写起来可能比开发需求更费时间。
|
||||
|
||||
## 测试的智能时代
|
||||
|
||||
“人人都可以是测试”,虽然在稳定性、兼容性又或者是性能测试的一些场景上,我们做得非常不错,但是对于某些自动化测试场景,特别是UI测试,目前还达不到人工测试的水平。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/49/b3/49c4ccf69ef9a2bbd5048abdf77733b3.png" alt="">
|
||||
|
||||
就拿UI测试为例,由于版本迭代周期越来越短,而且UI变动又非常频繁,无论是开发还是业务测试人员,对写测试脚本和用例的积极性都不是很高。由于测试脚本的编写成本和维护成本比较高,可复用程度又比较低,所以UI测试往往费时费力,很多时候效率还不如人工测试。
|
||||
|
||||
那测试从效能时代走向下一阶段,在智能时代我们应该怎样去解决这些问题呢?
|
||||
|
||||
**1. AI在测试的应用**
|
||||
|
||||
AI技术在我们熟悉的围棋和星际争霸的人机大战中已经大放异彩了,那它在测试领域可以擦出哪些不一样的火花呢?
|
||||
|
||||
先看看我们在UI自动化测试中遇到的几个困境:
|
||||
|
||||
<li>
|
||||
**覆盖率**。自动化测试可以覆盖多少场景,如何解决登录、网络异常等各种各样情况的干扰。
|
||||
</li>
|
||||
<li>
|
||||
**效率**。我们是否可以提高写测试用例的效率,或者是降低它的编写成本,做到人人都可以写用例。
|
||||
</li>
|
||||
<li>
|
||||
**准确性**。如何提高整个自动化测试过程的稳定性,对于测试流程和最终结果,是否还需要大量的人工干预分析。
|
||||
</li>
|
||||
|
||||
网易开源的[Airtest](https://github.com/AirtestProject/Airtest)、爱奇艺的[AIon](https://mp.weixin.qq.com/s/pIdNMQb6G1BEXMiYYKrwlQ),都尝试利用AI技术解决测试用例的编写效率和门槛问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6a/33/6a6b138dbd18fb3ce3c70aa52b02fe33.png" alt="">
|
||||
|
||||
这里主要用到图像识别以及OCR技术,以爱奇艺的Alon为例,它的整个处理流程是:
|
||||
|
||||
<li>
|
||||
**图片处理**。首先是场景的识别,例如当前界面是否有弹出对话,是否是登陆页面等,然后通过对截屏进行图像分割。这里的难点在于文字的OCR识别以及布局的分类,例如怎么样把不同的切割图像进行分类,并且能够知道这块图像对应的内容。
|
||||
</li>
|
||||
<li>
|
||||
**结果判定**。如何判定本次UI测试的结果是否是符合预期的,相似度达到多高的程度等。因为UI测试可能遇到的情况有很多,我们需要尽可能提升测试的准确性,减少人工干预的情况。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c1/c4/c173d58e5d10319499b6843e2c0562c4.png" alt="">
|
||||
|
||||
在Alon的参考文章中,还提到UI2Code这样一个应用场景,也就是把一个应用截图,或者把一个UI设计图,通过图像识别生成对应的代码。其实就是[Pixel to App](http://pixeltoapp.com/)希望实现的效果,相信也是很多开发人员的梦想吧,让我们可以彻底从UI开发中解放出来,通过设计稿就可以直接生成最终的UI代码。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bc/7e/bcce8b10091f70b73170942891155b7e.png" alt="">
|
||||
|
||||
Airtest和Alon解决的核心问题是UI自动化测试的效率,但是它们依然需要人工去编写测试用例。对于稳定性测试,虽然我上面提到过腾讯的New Monkey,不过它依然存在很多缺陷:
|
||||
|
||||
<li>
|
||||
**覆盖场景**。虽然是改进版本的Monkey,但是它依然覆盖不了所有的用户场景,而且很多场景它的执行流程不同于真实的用户。
|
||||
</li>
|
||||
<li>
|
||||
**非智能**。这里你可以理解为非人类,Monkey的操作和人类的操作方式完全不同。
|
||||
</li>
|
||||
|
||||
那有没有更智能的解决方案呢?Facebook的[Sapienz](https://code.fb.com/developer-tools/sapienz-intelligent-automated-software-testing-at-scale/)尝试希望像真实用户一样去使用我们开发的应用,它通过收集真实用户的操作路径来训练测试行为。而且在测试出崩溃后,Sapienz会自动关联和定位代码,提升解决问题的效率。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/af/3c/affdab2f6e3f743d349e3337515f9e3c.png" alt="">
|
||||
|
||||
虽然AI测试目前还无法完全取代人工测试,但随着技术的进一步成熟,相信它对测试领域的变革将会是革命性的。当然想要实现这个目标我们还有很多工作要做,这也意味着这其中还有很多技术创新的机会。
|
||||
|
||||
**2. 大数据在测试的应用**
|
||||
|
||||
现在越来越多的业务正在使用数据驱动的方式运作,测试的对象要从简单的代码转变为数据和算法。现在的业务越来越复杂,数据量越来越大,我们应该怎样及时发现产品的质量和业务问题呢?
|
||||
|
||||
国内的一些公司提出了基于大数据的“实时质量”体系,希望通过实时获取线上海量数据,完成业务数据校验和质量风险的感知。
|
||||
|
||||
这里的数据主要包括质量和业务两个方面。
|
||||
|
||||
<li>
|
||||
**质量**。崩溃、启动速度、卡顿以及联网错误等质量问题,我们希望可以做到分钟级别的实时性,同时支持更加细粒度的维度分析。例如可以通过国家、城市、版本等维度来查看网络问题。
|
||||
</li>
|
||||
<li>
|
||||
**业务**。对于产品的核心业务我们需要实现数据收集、分析与校验,打造数据监控和跟踪的能力。
|
||||
</li>
|
||||
|
||||
对于基于大数据的“实时质量”测试体系,关键在于如何保证数据的实时性与准确性,这两点我会在“数据评估”的内容中与你详细讨论。
|
||||
|
||||
除了质量和业务数据,大公司做得比较多的还有用户反馈和舆情的跟踪和分析。各大公司基本都有自己的一套系统,通过爬取用户反馈、应用市场、微博、新闻资讯等各方面的消息来源,监控产品的舆情情况,你可以参考[支付宝如何为移动端产品构建舆情分析体系](https://mp.weixin.qq.com/s/fjOEO8kKpNZ8x8FkfHUceA)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3e/7e/3e912bb7b93b5349ad42ba8eb315c87e.png" alt="">
|
||||
|
||||
## 总结
|
||||
|
||||
在APM系统搭建和持续交付优化过程,我接触了很多测试工程师,也关注了各大公司测试的现状。对于测试的职业道路发展,我个人的建议是:不管测试如何发展,测试效率如何提升,测试人员都要学会不断地变革,变革自己、变革整个研发流程。我们不能只守着自己的一亩三分田,需要尝试去做很多以前开发需要做的事情,比如性能、稳定性、安全等方面的工作。说得严重一点,如果你不能及时地更新自己的技术栈,不去往更深入的底层走,在测试的智能时代,首先淘汰的就是传统的功能测试人员。
|
||||
|
||||
在这个变革的时代,我身边也有很多独当一面的测试工程师通过平台化的机遇晋级为专家,也有一些优秀的测试转向了产品或研发,所以说还是要提高自身的能力,把机会握在自己手上。
|
||||
|
||||
“学如逆水行舟,不进则退”,对于开发人员同样如此。现在平台工具和框架越来越成熟,很多初学者拿着一大堆开源工具,也能写出炫酷的界面。如果我们不去进步,特别是在大环境不好的时候,也会很容易被淘汰。
|
||||
|
||||
今天我分享了一些我对高效测试的心得和体会,如果同学们里有测试领域的专家,非常欢迎你来谈谈对这个行业和发展的看法,分享一下你对高效测试的看法。
|
||||
|
||||
## 课后作业
|
||||
|
||||
你所在的公司,目前测试正在处于哪个阶段?对于测试,你还有哪些疑问?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
今天的课后作业是,请你观看网易、爱奇艺以及Facebook关于AI在测试领域应用的分享,在留言中写下自己的心得体会。
|
||||
|
||||
<li>
|
||||
网易:[基于AI的网易UI自动化测试方案与实践](https://github.com/AndroidAdvanceWithGeektime/Chapter28/blob/master/GMTC2018-%E3%80%8A%E5%9F%BA%E4%BA%8EAI%E7%9A%84%E7%BD%91%E6%98%93UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E6%96%B9%E6%A1%88%E4%B8%8E%E5%AE%9E%E8%B7%B5%E3%80%8B-%E7%86%8A%E5%8D%9A.pdf)
|
||||
</li>
|
||||
<li>
|
||||
爱奇艺:[基于AI的移动端自动化测试框架的设计与实践](https://github.com/AndroidAdvanceWithGeektime/Chapter28/blob/master/%E4%BD%95%E6%A2%81%E4%BC%9F-12.6-%E5%9F%BA%E4%BA%8EAI%E7%9A%84%E7%A7%BB%E5%8A%A8%E7%AB%AF%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E8%B7%B5.pdf)
|
||||
</li>
|
||||
<li>
|
||||
Facebook:[Automated fault-finding with Sapienz](https://www.facebook.com/atscaleevents/videos/2133728903555953/)
|
||||
</li>
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
205
极客时间专栏/Android开发高手课/模块二 高效开发/29 | 从每月到每天,如何给版本发布提速?.md
Normal file
205
极客时间专栏/Android开发高手课/模块二 高效开发/29 | 从每月到每天,如何给版本发布提速?.md
Normal file
@@ -0,0 +1,205 @@
|
||||
<audio id="audio" title="29 | 从每月到每天,如何给版本发布提速?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/00/2a5503adc8ee2b7d56d540420d99bd00.mp3"></audio>
|
||||
|
||||
还记得我们在持续交付设定的目标吗?我前面提到过,天猫的效能目标是“211”,也就是2周交付周期、1周开发周期以及1小时发布时长。对于一些更加敏捷的产品,我们可能还会加快到每周一个版本。在如此快的节奏下,我们该如何保证产品的质量?还有哪些手段可以进一步为发布“提速保质”?
|
||||
|
||||
更宽泛地说,广义的发布并不仅限于把应用提交到市场。灰度、A/B测试 、运营活动、资源配置…我们的发布类型越来越多,也越来越复杂。该如何建立稳健的发布质量保障体系,防止出现线上事故呢?
|
||||
|
||||
## APK的灰度发布
|
||||
|
||||
我们在讨论版本发布速度,是需要兼顾效率和质量。如果不考虑交付质量,每天一个版本也很轻松。在严格保证交付质量的前提下,两周发布一个版本其实并不容易。特别是出现紧急安全或者稳定性问题的时候,我们还需要有1小时的发布能力。
|
||||
|
||||
正如我在专栏“[如何高效地测试](https://time.geekbang.org/column/article/83417)”中说的,实现“高质高效”的发布需要强大的测试平台和数据验证平台的支撑。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a8/77/a850639f20e8802296a97dfa92e3bd77.png" alt="">
|
||||
|
||||
下面我们一起来看看影响版本发布效率的那些重要因素,以及我对于提升版本发布速度的实践经验。
|
||||
|
||||
**1. APK灰度**
|
||||
|
||||
测试平台负责对发布包做各种维度的诊断测试,通常会包括Monkey测试、性能测试(启动、内存、CPU、卡顿等)、竞品测试、UI测试、弱网络测试等。但是即使通过云测平台能够同时测试几十上百台机器,本地测试依然无法覆盖所有的机型和用户路径。
|
||||
|
||||
为了安全稳定地发布新版本,我们需要先圈定少量用户安装试用,这就是灰度发布。而数据验证平台则负责收集灰度和线上版本的应用数据,这里可能包括性能数据、业务数据、用户反馈以及外部舆情等。
|
||||
|
||||
所以说,灰度效率首先被下面两个因素所影响:
|
||||
|
||||
<li>
|
||||
**测试效率**。虽然灰度发布只影响少部分用户,但是我们需要尽可能保障应用的质量,以免造成用户流失。测试平台的发布测试时间是影响发布效率的第一个因素,我们希望可以在1小时内明确待定的发布包是否达到上线标准。
|
||||
</li>
|
||||
<li>
|
||||
**数据验证效率**。数据的全面性、实时性以及准确性都会影响灰度版本的评估决策时间,是停止灰度发布,还是进一步扩大灰度的用户量级,或者可以直接发布到全量用户。对于核心数据,需要建立小时甚至分钟级别的实时监控。**比如微信,对于性能数据可以在发布后1小时内评估完毕,而业务数据可以在24小时内评估完毕**。
|
||||
</li>
|
||||
|
||||
另外一方面,如果我们的灰度发布想覆盖一万名用户,那多长时间才有足够的用户下载和安装呢?渠道的能力对灰度发布效率的影响也十分巨大,在国内主要有下面几个灰度渠道。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4c/50/4c6faedf13a83ad1af0312cabb68b250.png" alt="">
|
||||
|
||||
在国内由于没有统一的应用商店,灰度渠道效率的确是一个非常严峻的问题。即使是微信,如果不动用“红点提示”这个大杀器,每天灰度发布到的用户量可能还不到十万。而国际市场有统一的Google Play,可以通过[Google Beta](http://developer.android.com/distribute/best-practices/launch/beta-tests?hl=zh-cn)进行灰度。但是版本发布需要考虑GP审核的时间,目前GP审核速度相比之前有所加快,一般只需要一到两天时间。
|
||||
|
||||
通过灰度发布我们可以提前收集少部分用户新版本的性能和业务数据,但是它并不适用于精确评估业务数据的好坏。这主要是因为灰度的用户是有选择的,一般相对活跃的用户会被优先升级。
|
||||
|
||||
**2. 动态部署**
|
||||
|
||||
对于灰度发布,整个过程最大的痛点依然是灰度包的覆盖速度问题。而且传统的灰度方式还存在一个非常严重的问题,那就是无法回退。“发出去的包,就像泼出去的水”,如果出现严重问题,还可能造成灰度用户的流失。
|
||||
|
||||
Tinker动态部署框架正是为了解决这个问题而诞生的,我们希望Tinker可以成为一种新的发布方式,用来取代传统的灰度甚至是正式版本的发布。相比传统的发布方式,热修复有很多得天独厚的优势。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a5/42/a5e29c97ec475509526553e5b50e7442.png" alt="">
|
||||
|
||||
<li>
|
||||
**快速**。如果使用传统的发布方式,微信需要10天时间覆盖50%的用户。而通过热修复,在一天内可以覆盖80%以上的用户,在3天内可以覆盖95%以上的用户。
|
||||
</li>
|
||||
<li>
|
||||
**可回退**。当补丁出现重大问题的时候,可以及时回退补丁,让用户回到基础版本,尽可能降低损失。
|
||||
</li>
|
||||
|
||||
为了提升补丁发布的效率,微信还专门开发了TinkerBoots管理平台。TinkerBoots平台不仅支持人数、条件等参数设置,例如可以选择只针对小米的某款机型下发10000人的补丁;而且平台也会打通数据验证平台,实现自动化的控量发布,自动监控核心指标的变化情况,保证发布质量。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fa/e0/fad6c167189e011ea725768b6dc49ee0.png" alt="">
|
||||
|
||||
Tinker发布已经两年多了,虽然热修复技术可以解决很多问题,但作为Tinker的作者,我必须承认它对国内的Android开发造成了一些不好的影响。
|
||||
|
||||
- **用户是最好的测试**。很多团队不再信奉前置的测试平台,他们认为反正有可以回退的动态部署发布,出现质量问题并不可怕,多发几个补丁就可以了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/37/b5/378b7e3e2fffa666ddf0286a8eef6ab5.png" alt="">
|
||||
|
||||
- **性能低下**。正如专栏“高质量开发”模块所说的,热修复、组件化这些黑科技会对应用的性能产生不小的影响,特别是启动的耗时。
|
||||
|
||||
从现在看来,热修复并不能取代发布,它更适合使用在少量用户的灰度发布。如果不是出现重大问题,一般情况也不应该发布针对所有用户的补丁。
|
||||
|
||||
组件化回归模块化,热修复回归灰度,这是国内很多大型App为了性能不得不做出的选择。如果想真正实现“随心所欲”的发布,可能需要倒逼开发模式的变革,例如从组件化转变为Web、React Native/Weex或者小程序来实现。
|
||||
|
||||
## A/B测试
|
||||
|
||||
正如我前面所说,APK灰度是对已有功能的线上验证,它并不适合用于准确评估某个功能对业务的影响。
|
||||
|
||||
>
|
||||
<p>If you are not running experiments, you are probably not growing!<br>
|
||||
——by Sean Ellis</p>
|
||||
|
||||
|
||||
Sean Ellis是[增长黑客模型(AARRR)](https://www.jianshu.com/p/183a89c91e9f)之父,增长黑客模型中提到的一个重要思想就是“A/B测试”。 Google、 Facebook、国内的头条和快手等公司都非常热衷于A/B测试,希望通过测试进行科学的、数据驱动式的业务和产品决策。
|
||||
|
||||
那究竟什么是A/B测试?如何正确地使用A/B测试呢?
|
||||
|
||||
**1. 什么是A/B测试**
|
||||
|
||||
可能有同学会认为,A/B测试并不复杂,只是简单地把灰度用户分为A和B两部分,然后通过对比收集到的数据,分析得到测试的结论。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cc/92/cc9b985f2fb64170e407754700a05c92.png" alt="">
|
||||
|
||||
事实上A/B测试的难点就是A和B用户群体的圈定,我们需要保证测试方案是针对同质人群、同一时间进行,确保了除方案变量之外其他变量都是一致的,这样才能将指标数据差异归到产品方案,从而选出优胜版本在线上发布,实现数据增长。
|
||||
|
||||
<li>
|
||||
**同质**。指人群的各种特征分布的一致性。例如我们想验证产品方案对女性用户的购买意愿的影响,那么A版本和B版本选择的用户必须都是女性。当然特征分布不光是性别,还有国家、城市、使用频率、年龄、职业、新老用户等。
|
||||
</li>
|
||||
<li>
|
||||
**同时**。在不同的时间点,用户的行为可能不太一样。例如在一些重大的节日,用户活跃度会升高,那如果A方案的作用时间在节日,B方案的作用时间在非节日,很显然这种比较对于B方案是不公平的。
|
||||
</li>
|
||||
|
||||
所以实现“同质同时”,并不是那么简单,首先需要丰富和精准的用户画像能力,例如版本、国家、城市、性别、年龄、喜好等用户属性。除此之外,还需要一整套强大的后台,完成测试控制、日志处理、指标计算、统计显著性指标等工作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3b/e1/3bc2a204cf9e8f79bd4031c08efe8ae1.png" alt="">
|
||||
|
||||
实现了“同质同时”之后,我们接着要找到产品方案的显著性指标,也就是方案想要证明的目标。例如我们优化了弹窗的提示语,目的是吸引更多的用户点击按钮,那按钮的点击率就是这个测试的显著性指标。
|
||||
|
||||
有了这些以后,那我们是不是就可以愉快地开始测试了?不对,你还需要先思考这两个问题:
|
||||
|
||||
- **流量选择**。这个测试应该配置多少流量?配少了怕得不出准确的测试结论,配置多了可能要承担更大的风险。这个时候就需要用到[最小样本量计算器](http://www.evanmiller.org/ab-testing/sample-size.html#!99.977;95;5;0.01;0)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/51/d8/51b98ef396e91f77b9855f5d570ed4d8.png" alt="">
|
||||
|
||||
A/B测试作为一种抽样统计,它背后涉及大量的[统计学原理](http://help.appadhoc.com/zh/dataDecision/ABC.html),而这个计算器需要我们提供以下数值:
|
||||
|
||||
>
|
||||
<p>**基准线指标值**:请输入测试要优化的指标的基准值,比方“某按钮的点击率”,如果该值每日在9%~11%之间波动,则可输入10%。<br>
|
||||
**指标最小相对变化**:请输入你认为有意义的最小相对变动值。假设你将要优化的指标为10%时,你在最小相对变化中输入5%, 就意味着你认为绝对值变化在(9.5%, 10.5%)之间的变动没有意义,即便此时测试版本更优,你也不会采用。<br>
|
||||
**统计功效1−β**:如果设置统计功效为90%,可通俗理解为,在A/B测试中,当版本A和版本B的某项统计指标本质上存在显著差异时,可以正确地识别出版本A和版本B是有显著差异的概率是90%。<br>
|
||||
**显著性水平α**:如果统计指标的差异超过具体的差异,我们才说测试的结果是显著的。</p>
|
||||
|
||||
|
||||
- **天数选择**。A/B测试需要持续几天,我们才可以认为测试是可靠的。一般来说可以通过下面的方法计算。
|
||||
|
||||
```
|
||||
测试所需持续的天数 >= 计算获得用户数 / (场景日均流量 * 测试版本设置流量百分比)
|
||||
|
||||
```
|
||||
|
||||
**2. 如何进行A/B测试**
|
||||
|
||||
虽然各个大厂都有自己完善的A/B测试平台,但是A/B测试的科学设计并不容易,需要不断地学习,再加上大量的实践经验。
|
||||
|
||||
首先来说,所有的A/B测试都应该是有“预谋”的,也就是我们需要有对应的预期,需要设计好测试的每一个环节。一般来说,在测试开始之前,需要问清楚自己下面这些问题:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/64/ed/644578eb8364674e93674c1b983937ed.png" alt="">
|
||||
|
||||
为了测试埋点、分流、统计的正确性,以及增加A/B测试的结论可信度,我们还会在A/B测试的同时,增加A/A测试。A/A测试是A/B测试的“孪生兄弟”,有的互联网公司也叫空转测试。它主要用于评估我们测试本身是否科学,一般我推荐使用A/A/B的测试方式。
|
||||
|
||||
那在Android客户端有哪些实现A/B测试的方案呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a8/43/a8cf8e2e7b3f95a65281b746fda42f43.png" alt="">
|
||||
|
||||
用一句话来描述A/B测试的话就是,“拿到A/B测试的数据容易,拿到可信的A/B测试的数据却很难”,因此在指标设计、人群选择、时间设计、方案的设计都需要考虑清楚。同时也需要多思考、多实践,推荐你拓展阅读[《移动App A/B测试中的5种常见错误》](https://zhuanlan.zhihu.com/p/25319221)。
|
||||
|
||||
## 统一发布平台
|
||||
|
||||
我在专栏里多次提到,虽然我们做了大量的优化,依然受限于原生开发模式的各种天花板的限制。这时可以考虑跳出这些限制,例如Web、React Native/Weex、小程序和Flutter都可以是解决问题的思路。
|
||||
|
||||
但是即使我们转变为新的开发模式,灰度和发布的步骤依然是不可或缺的。此外,我们还要面对各种各样的运营活动、推送弹窗、配置下发。
|
||||
|
||||
可能很多大厂的同学都深受其苦,面对各式各样的发布平台,一不小心就可能造成运营事故。那应该如何规范发布行为,避免出现下发事故呢?
|
||||
|
||||
**1. 发布平台架构**
|
||||
|
||||
每个应用都或多或少涉及下面这些发布类型,但是往往这些发布类型都分别放在大大小小的各种平台中,而且没有统一的管理规范流程,非常容易因为操作错误造成事故。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a5/32/a5d98e799524d9c2275fa20e0e2f3a32.png" alt="">
|
||||
|
||||
统一发布平台需要集中管理应用所有的数据下发业务,并建立严格规范的灰度发布流程。
|
||||
|
||||
<li>
|
||||
**管理**。所有的发布都必须通过权限校验,需要经过审批。“谁发布,谁负责”,需要建立严格的事故定级制度。对于因为疏忽导致事故的人员,需要定级处理。
|
||||
</li>
|
||||
<li>
|
||||
**灰度**。所有的发布一定要经过灰度测试,慢慢扩大影响的用户范围。但是需要承认,某些下发并不容易测试,例如之前沸沸扬扬的“圣诞节改变展示样式”的事件。对于这种在特定时间生效的运营活动,很难在线上灰度验证。
|
||||
</li>
|
||||
<li>
|
||||
**监控**。统一发布平台需要对接应用的“实时数据平台”,在出现问题的时候,需要及时采取补救措施。
|
||||
</li>
|
||||
|
||||
业务已经那么艰难了,如果下发了一个导致所有用户启动崩溃的闪屏活动,对应用造成的损失就难以衡量。所以规范的流程和章程,可以一定程度上避免问题的发生。当然监控也同样重要,它可以帮助我们及时发现问题并立刻止损。
|
||||
|
||||
**2. 运营事故的应对**
|
||||
|
||||
每天都有大量各种类型的发布,感觉就像在刀尖上行走一样。“人在江湖飘,哪能不挨刀”,当我们发现线上运营问题的时候,还有哪些挽救的措施呢?
|
||||
|
||||
<li>
|
||||
[**启动安全保护**](https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488429&idx=1&sn=448b414a0424d06855359b3eb2ba8569&source=41#wechat_redirect)。最低限度要保证用户不会由于运营配置导致无法启动应用。
|
||||
</li>
|
||||
<li>
|
||||
**动态部署**。如果应用可以正常启动,那我们可以通过热修复的方式解决。但是热修复也存在一定的局限性,例如有3%~5%的用户会无法热修复成功,而且也无法做到立即生效。
|
||||
</li>
|
||||
<li>
|
||||
**远程控制**。在应用中,我们还需要保留一条“救命”的指令通道。通过这条通道,可以执行删除某些文件、拉取补丁、修改某些配置等操作。但是为了避免自有通道失效,这个控制指令需要同时支持厂商通道,例如某个下发资源导致应用启动后界面白屏,这个时候我们可以通过下发指令去删除有问题的资源文件。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/42/2b/420bcf69de8d3aaa6750b62fc59ed92b.png" alt="">
|
||||
|
||||
“万丈高楼平地起”,类似测试平台、发布平台或者数据平台这些内部系统很难短时间内做得非常完善,它们都是通过优化内部无数的小细节演进而来。所以我们需要始终保持耐心,朝着提升组织效能的方向前进。
|
||||
|
||||
## 总结
|
||||
|
||||
在过去那个做A/B测试非常艰难而且耗时耗力的年代,测试的每个环节都会经过千锤百炼。而现在通过功能强大的A/B测试系统,极大地降低测试的成本,提高了测试的效率,反而很多产品和开发人员变得有点滥用A/B测试,或者说有了“不去思考”的借口。
|
||||
|
||||
无论是研发主导的性能相关的A/B测试,还是产品主导的业务相关的A/B测试,其实很多时候都没有经过严谨的推敲,往往需要通过反反复复多次测试才能得到一个“结论”,而且还无法保证是可信的。所以无论是A/B测试,还是日常的灰度,都需要有明确的预期,真正去推敲里面的每一个环节。不要每次测试发布后,才发现实验设置不合理,或者发现这里那里漏了好几个数据打点,再反反复复进行修改,对参与测试的所有人来说都非常痛苦。
|
||||
|
||||
另外一方面,我们对某个事情的看法并不会一成不变。即使我是Tinker的作者,我也认为它只是某一阶段为了解决特定需求的产物。但是无论开发模式怎么改变,我们对质量和效率的追求是不会改变的。
|
||||
|
||||
## 课后作业
|
||||
|
||||
你的应用是否是A/B测试的狂热分子?对于A/B测试,你有哪些好的或者坏的的经历?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
今天的课后作业是,思考自己的产品或者公司在灰度发布过程中存在哪些痛点?还有哪些优化的空间?请在留言中写下自己的心得体会。
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
187
极客时间专栏/Android开发高手课/模块二 高效开发/30 | 数据评估(上):如何实现高可用的上报组件?.md
Normal file
187
极客时间专栏/Android开发高手课/模块二 高效开发/30 | 数据评估(上):如何实现高可用的上报组件?.md
Normal file
@@ -0,0 +1,187 @@
|
||||
<audio id="audio" title="30 | 数据评估(上):如何实现高可用的上报组件?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/6d/2a09f320f73b24ffce5f1e032ea5b56d.mp3"></audio>
|
||||
|
||||
无论是“高效测试”中的实时监控,还是“版本发布”中的数据校验平台,我都多次提到了数据的重要性。
|
||||
|
||||
对于数据评估,我们的期望是“又快又准”。“快”,表示数据的时效性。我们希望在1小时内,甚至1分钟内就可以对数据进行评估,而不需要等上1天或者几天。“准”,表示数据的准确性,保证数据可以反映业务的真实情况,不会因为数据不准确导致做出错误的产品决策。
|
||||
|
||||
但是“巧妇难为无米之炊”,数据平台的准确性和时效性依赖客户端数据采集和上报的能力。那应该如何保证客户端上报组件的实时性和准确性?如何实现一个“高可用”的上报组件呢?
|
||||
|
||||
## 统一高可用的上报组件
|
||||
|
||||
可能有同学会疑惑,究竟什么是“高可用”的上报组件?我认为至少需要达到三个目标:
|
||||
|
||||
<li>
|
||||
**数据不会丢失**。数据不会由于应用崩溃、被系统杀死这些异常情况而导致丢失。
|
||||
</li>
|
||||
<li>
|
||||
**实时性高**。无论是前台进程还是后台进程,所有的数据都可以在短时间内及时上报。
|
||||
</li>
|
||||
<li>
|
||||
**高性能**。这里主要有卡顿和流量两个维度,应用不能因为上报组件的CPU和I/O过度占用导致卡顿,也不能因为设计不合理导致用户的流量消耗过多。
|
||||
</li>
|
||||
|
||||
但是数据的完整性、实时性和性能就像天平的两端,我们无法同时把这三者都做到最好。因此我们只能在兼顾性能的同时,尽可能地保证数据不会丢失,让上报延迟更小。
|
||||
|
||||
在“网络优化”中,我不止一次的提到网络库的统一。网络库作为一个重要的基础组件,无论是应用内不同的业务,还是Android和iOS多端,都应该用同一个网络库。
|
||||
|
||||
同理,上报组件也是应用重要的基础组件,我们希望打造的是统一并且高可用的上报组件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bf/9a/bf769e6e280ebeb1f8d16cbe95fe5e9a.png" alt="">
|
||||
|
||||
一个数据埋点的过程,主要包括采样、存储、上报以及容灾这四个模块,下面我来依次拆解各个模块,一起看看其中的难点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/19/83/1933f4cf48667805a18e07ec425ea783.png" alt="">
|
||||
|
||||
**1. 采样模块**
|
||||
|
||||
某些客户端数据量可能会非常大,我们并不需要将它们全部都上报到后台。比如说卡顿和内存这些性能数据,我们只需要抽取小部分用户统计就可以了。
|
||||
|
||||
采样模块是很多同学在设计时容易忽视的,但它却是所有模块中最为复杂的一项,需要考虑下面一些策略的选择。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/61/70/6175884b6d62d6ad5b974baa6c723870.png" alt="">
|
||||
|
||||
大多数的组件采用的都是PV次数采样,这样的确是最简单的。但是我们更多是在性能数据埋点上采样,为了降低用户的影响面,我更加倾向于使用UV采样的方式。而且为了可以让更多的用户上报,我也希望每天都可以更换一批新的用户。
|
||||
|
||||
最终我选择的方案是“UV采样 + 用户标识随机 + 每日更换用户”的方式,但是采样还需要满足三个标准。
|
||||
|
||||
<li>
|
||||
**准确性**。如果配置了1%的采样比例,需要保证某一时刻只有1%的用户会上报这个数据。
|
||||
</li>
|
||||
<li>
|
||||
**均匀性**。如果配置了1%的采样比例,每天都会更换不同的1%用户来上报这个数据。
|
||||
</li>
|
||||
<li>
|
||||
**切换的平滑性**。用户的切换需要平滑,不能在用一个时间例如12点,所有用户同时切换,这样会导致后台数据不连贯。
|
||||
</li>
|
||||
|
||||
实现上面这三个标准并不容易,在微信中我们采用了下面这个算法:
|
||||
|
||||
```
|
||||
// id:用户标识,例如微信号、QQ号
|
||||
id_index = Hash(id) % 采样率倒数
|
||||
time_index = (unix_timestamp / (24*60*60)) % 采样率倒数
|
||||
上报用户 =(id_index == time_index)
|
||||
|
||||
```
|
||||
|
||||
每个采样持续24小时,使整个切换可以很平滑,不会出现所有用户同时在0点更换采样策略。有些用户在早上10点切换,有些用户在11点切换,会分摊到24小时中。并且从一个小时或者一天的维度来看,也都可以保证采样是准确的。
|
||||
|
||||
不同的埋点可以设置不同的采样率,它们之间是独立的、互不影响的。当然除了采样率,在采样策略里我们还可以增加其他的控制参数,例如:
|
||||
|
||||
<li>
|
||||
**上报间隔**:可以配置每个埋点的上报间隔,例如1秒、1分钟、10分钟、60分钟等。
|
||||
</li>
|
||||
<li>
|
||||
**上报网络**:控制某些点只允许WiFi上传。
|
||||
</li>
|
||||
|
||||
**2. 存储模块**
|
||||
|
||||
对于存储模块,我们的目标是在兼顾性能的同时,保证数据完全不会丢失。那应该如何实现呢?我们首先要思考进程和存储模式的选择。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c2/56/c279b91e975100271abb804280ed2356.png" alt="">
|
||||
|
||||
业内最常见的上报组件是“单进程写 + 文件存储 + 内存缓存”,虽然这种方式实现最为简单,但是无论是跨进程的IPC调用堆积(IPC调用总是很慢的)还是内存缓存,都可能会导致数据的丢失。
|
||||
|
||||
回顾一下在“I/O优化”中,我列出的mmap、内存与写文件的性能对比数据。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a8/8e/a898e4578fe7724cc654fa9acb3cdf8e.png" alt="">
|
||||
|
||||
你可以看到mmap的性能非常不错,所以我们最终选择的是 **“多进程写 + mmap”的方案,并且完全抛弃了内存缓存**。不过mmap的性能也并不完美,它在某些时刻依然会出现异步落盘,所以每个进程mmap操作需要放到单独的线程中处理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a8/87/a8dfe16bc5476e7e21b755e5a62d4c87.png" alt="">
|
||||
|
||||
**“多进程写 + mmap”的方案可以实现完全无锁、无IPC并且数据基本不会丢失**,看上去简直完美,但是真正实现时是不是像图中那么简单呢?肯定不会那么简单,因为我们需要考虑埋点数据的聚合以及上报数据优先级。
|
||||
|
||||
<li>
|
||||
**埋点数据的聚合**。为了减少上报的数据量,尤其是部分性能埋点,我们需要支持聚合上报。大部分组件都是使用上报时聚合的方式,但是这样无法解决存储时的数据量问题。由于我们使用的是mmap,可以像操作内存一样操作文件中的数据,可以实现性能更优的埋点数据的聚合功能。
|
||||
</li>
|
||||
<li>
|
||||
**上报数据优先级**。很多上报组件埋点时都会使用一个是否重要的参数,对于重要的数据采用直接落地的方式。对于我们的方案来说,已经默认所有的数据都是重要的。关于上报数据的优先级,我建议使用上报间隔来区分,例如1分钟、10分钟或者1小时。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/74/88/747ab81587c7c5d0ceebe4e8763aad88.png" alt="">
|
||||
|
||||
对于一些敏感数据,可能还需要支持加密。对于加密的数据,我建议使用单独的另一个mmap文件存储。
|
||||
|
||||
为什么上面我说的是数据基本不会丢失,而不是完全不会丢失呢?因为当数据还没有mmap落盘,也就是处于采样、存储内部逻辑时,这个时候如果应用崩溃依然会造成数据丢失。为了减少这种情况发生,我们做了两个优化。
|
||||
|
||||
<li>
|
||||
**精简处理逻辑**。尽量减少每个埋点的处理耗时,每个埋点的数据处理时间需要压缩到0.1毫秒以内。
|
||||
</li>
|
||||
<li>
|
||||
**KillProcess等待**。在应用主动KillProcess之前,需要调用单独的函数,先等待所有队列中的埋点处理完毕。
|
||||
</li>
|
||||
|
||||
**3. 上报模块**
|
||||
|
||||
对于上报模块,我们不仅需要满足上报实时性,还需要合理地优化流量的使用,主要需要考虑的策略有:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/66/5e995f63ce97465825cdd0cfea669466.png" alt="">
|
||||
|
||||
为了解决后台进程的上报实时性问题,我们采用了单进程上报的策略,我推荐使用保活能力比较强的进程作为唯一的上报进程。为了更加精细地控制上报间隔,我们采用更加复杂的班车制度模式。
|
||||
|
||||
后来经过仔细思考,最终的上报模块采用“多进程写 + 单进程上报”。这里有一个难点,那就是如何及时的收集所有已经停止的班车,会不会出现多进程同步的问题呢?我们是通过Linux的文件rename的原子性以及FileObserver机制,实现了一套完全无锁、高性能的文件同步模型。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c6/cd/c67bb5cb84ecc577e00f13af3d6c9bcd.png" alt="">
|
||||
|
||||
每个进程在对应优先级的文件“停车”的时候,负责把文件rename到上报数据存放的目录中。因为rename是原子操作,所以不用担心两个进程会同时操作到同一个文件。而对应的上报进程只需要监听上报数据目录的变化,就可以实现文件状态的同步。这样就避免了多进程同步操作同一个文件的问题,整个过程也无需使用到跨进程的锁。
|
||||
|
||||
当然上报模块里的坑还有很多很多,例如合并上报文件时应该优先选择高优先级的文件;对于上报的包大小,在移动网络需要设置的比WiFi小一些,而不同优先级的文件需要合并组包,尽量吃满带宽;而且在弱网络的时候,我们需要把数据包设置得更小一些,先上报最高优先级的数据。
|
||||
|
||||
**4. 容灾模块**
|
||||
|
||||
虽然我们设计得上报模块已经很强大,但是如果使用者调用不合理,也可能会导致严重的性能问题。我曾经遇到过,某个同学在一个for循环连续埋了一百万个点;还有一次是某个用户因为长期没有网络,导致本地堆积了大量的数据。
|
||||
|
||||
一个强大的组件,它还需要具备容灾的能力,本地一般可以有下面这些策略。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/3e/f3aaa8e7762bba6c6b3a9f88faacf83e.png" alt="">
|
||||
|
||||
容灾模块主要是保证即使出现开发者使用错误、组件内部异常等情况,也不会给用户的存储空间以及流量造成严重问题。
|
||||
|
||||
## 数据自监控
|
||||
|
||||
通过“多进程写 + mmap + 后台进程上报 + 班车模式”,我们实现了一套完全无锁、数据基本不会丢失、无跨进程IPC调用的高性能上报组件,并且通过容灾机制,它还可以实现异常情况的自动恢复。
|
||||
|
||||
那线上效果是不是真的这么完美?我们怎样确保上报组件的数据可靠性和时效性呢?答案依然是**监控**,我们需要建立一套完善的自监控体系,为后续进一步优化提供可靠的数据支撑。
|
||||
|
||||
**1. 质量监控**
|
||||
|
||||
上报组件的核心数据指标主要包括以下几个:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9f/79/9f62e5c711224b8589bce6ec125ed279.png" alt="">
|
||||
|
||||
当然,如果我们追求更高的实时性,可以选择计算小时到达率,甚至是分钟到达率。
|
||||
|
||||
**2. 容灾监控**
|
||||
|
||||
当客户端出现容灾处理时,我们也会将数据单独上报到后台监控起来。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/59/f9/59cc80a728e17cecd23b710c29bbfdf9.png" alt="">
|
||||
|
||||
除了异常情况的监控,我们还希望将用户每日使用的移动流量和WiFi流量做更加细粒度的分区间监控,例如0~1MB的占比、1~5MB的占比等。
|
||||
|
||||
## 总结
|
||||
|
||||
网络和数据都是非常重要的基础组件,今天我们一起打造了一款跨平台、高可用的上报组件。这也是目前比较先进的方案,在各方面的质量指标都比传统的方案有非常大的提升。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/b4/022ec87c80a928d1fa536dcde99bb3b4.png" alt="">
|
||||
|
||||
当然真正落实到编码,这里面还有非常多的细节需要考虑,也还有大大小小很多暗坑。而且虽然我们使用C++实现,但是也还需要处理不同平台的些许差异,比如iOS根本不需要考虑多进程问题等。
|
||||
|
||||
在实践中我的体会是,当我们亲自动手去实现一个网络库或者上报组件的时候,才会深深体会到把一个新东西做出来并不困难,但是如果想要做到极致,那必然需要经过精雕细琢,更需要经过长时间的迭代和优化。
|
||||
|
||||
## 课后作业
|
||||
|
||||
你所在的公司,目前正在使用哪个数据上报组件?它存在哪些问题呢?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
今天的课后作业是,在实现方案中我故意隐去了两个细节点,这里把它们当作课后作业留给你,请你在留言中写下自己的答案。
|
||||
|
||||
**1. 采样策略的更新**。当我们服务器采样策略更新的时候,如果不使用推送,怎样保证新的采样策略可以以最快速度在客户端生效?<br>
|
||||
**2. 埋点进程突然崩溃**。你有没有想到,如果Process A突然崩溃,那哪个进程、在什么时机、以哪种方式,应该负责把Process A对应的埋点数据及时rename到上报数据目录?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c6/cd/c67bb5cb84ecc577e00f13af3d6c9bcd.png" alt="">
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
205
极客时间专栏/Android开发高手课/模块二 高效开发/31 | 数据评估(下):什么是大数据平台?.md
Normal file
205
极客时间专栏/Android开发高手课/模块二 高效开发/31 | 数据评估(下):什么是大数据平台?.md
Normal file
@@ -0,0 +1,205 @@
|
||||
<audio id="audio" title="31 | 数据评估(下):什么是大数据平台?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b2/b4/b269fa8eb68e1655952084ded81070b4.mp3"></audio>
|
||||
|
||||
数据是连接产品和用户的桥梁,它反映了用户对产品的使用情况,是我们作出业务决策的重要依据。虽然通过“高可用的上报组件”,可以从源头上保障数据采集的准确性和实时性,但是随着App业务迭代的复杂化,经常会出现遗漏埋点、错误埋点、多端埋点不统一等情况,影响了业务数据的稳定性。
|
||||
|
||||
我见过很多团队的埋点文档管理得非常不规范,有的还在使用Excel来管理埋点文档,经常找不到某些埋点的定义。而随着埋点技术和流程的成熟,我们需要有一整套完整的方案来保证数据的稳定性。
|
||||
|
||||
那埋点应该遵循什么规范?如何实现对埋点整个流程的引导和监控?埋点管理、埋点开发、埋点测试验证、埋点数据监控…怎样打造一站式的埋点平台?在埋点平台之上,大数据平台又是什么样的呢?
|
||||
|
||||
## 埋点的基础知识
|
||||
|
||||
我们知道,一个业务埋点的上线需要经历需求、开发、测试等多个阶段,会涉及产品、开发和测试多方协作,而对于大型团队来说,可能还要加上专门的数据团队。
|
||||
|
||||
对于传统埋点来说,错埋、漏埋这样的问题总会反反复复出现。为了排查和解决数据的准确性问题,参与的各方团队都要耗费大量的精力。特别是如果埋点一旦出现问题,我们还需要依赖App发布新版本,可见埋点的修复周期长,而且成本也非常巨大。
|
||||
|
||||
那应该如何解决这个问题呢?请先来思考一下,我们应该如何实现一个正确的埋点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d9/3f/d9a2df07038e18b1f143615cf36ce73f.png" alt="">
|
||||
|
||||
如果想实现一个正确的埋点,必须要满足上面的这四个条件,需要有非常严格的埋点流程管理。因此,你需要做到:
|
||||
|
||||
<li>
|
||||
**统一的埋点规范**。应用甚至是整个公司内部,从日志的格式、参数的含义都要有统一的规则。
|
||||
</li>
|
||||
<li>
|
||||
**统一的埋点流程**。在整个埋点过程,产品、开发、测试和数据团队都要肩负起各自的职责,一起通力协作,通过统一、规范的流程来实现正确的埋点。
|
||||
</li>
|
||||
|
||||
通过统一的埋点规范和流程,希望可以减少埋点开发的成本,保障数据的准确性。下面我们一起来看看具体应该如何实践。
|
||||
|
||||
**1. 统一埋点规范**
|
||||
|
||||
在打开淘宝的主页时,不知道你有没有注意到URL后面会带有一个SPM的参数。
|
||||
|
||||
>
|
||||
[https://www.taobao.com/?spm=a21bo.2017.201857.3.5af911d9ycCIDq](https://www.taobao.com/?spm=a21bo.2017.201857.3.5af911d9ycCIDq)
|
||||
|
||||
|
||||
这个SPM代表什么含义呢?SPM全称是Super Position Model,也就是超级位置模型。简单来说,它是阿里内部统一的埋点规范协议,无论H5还是Native的Android和iOS,都要遵循这套规范。
|
||||
|
||||
正如上面的链接一样,SPM由A.B.C.D四段构成,各分段分别代表的含义如下。
|
||||
|
||||
```
|
||||
A:站点/业务, B:页面, C:页面区块, D:区块内点位
|
||||
|
||||
注:a21bo.2017.201857.3.5af911d9ycCIDq一共有5位,这是网站特有的,最后一位分配的是一个随机特征码,只是用来保证每次点击SPM值的唯一性。
|
||||
|
||||
```
|
||||
|
||||
SPM主要有页面访问、控件点击以及曝光三种类型的事件,它可以用来记录了用户点击或者查看当前页面的具体信息,还可以推算出用户来自上一个页面的哪个位置。基于SPM规范,淘宝可以得到用户每个页面PV、点击率、停留时长、转化率、用户路径等各种维度的指标。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/47/d8/477efc10cb1310f660321192ec92d1d8.png" alt="">
|
||||
|
||||
对于埋点规范来说,从公共参数到内部的各个业务参数,我们都需要定义完整的日志格式。目前,SPM这套规范已经推广到阿里整个集团以及外部的合作伙伴中,这样通过各个部门、各个客户端的规范统一,不仅降低了内部学习和沟通协作的成本,而且对后续的数据存储、校验、分析都会带来极大的便利。
|
||||
|
||||
“一千个读者心中有一千个哈姆雷特”,每个公司的情况可能不一定相同,所以也不能保证阿里的埋点规范适合所有的企业。但是无论我们最终决定使用哪种规范,对于公司内部,至少是应用内部来说,埋点规范应该是统一的。
|
||||
|
||||
关于SPM规范,如果你想了解更多,可以参考[《SPM参数有什么作用》](https://www.zhihu.com/question/62813754)和[《阿里巴巴的日志采集分享》](https://blog.naaln.com/2017/08/alibaba-data-track-1/)。
|
||||
|
||||
**2. 统一埋点流程**
|
||||
|
||||
埋点的整个过程涉及产品、开发、测试、数据团队等多个团队,如果内部没有完善的流程规范,非常容易出现“四国大混战”。在出现数据问题的时候,也常常会出现互相推卸责任的情况。
|
||||
|
||||
“无规矩不成方圆”,我们需要制定统一的埋点流程,严格规范整个埋点过程的各个步骤以及每个参与者在相应步骤的分工和责任。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f0/e7/f0cc32f276e639ebf2320d0ba0f462e7.png" alt="">
|
||||
|
||||
<li>
|
||||
**需求阶段**。在需求评审阶段,产品需要列出具体的埋点需求。如果有数据团队的话,产品的埋点需求需要数据团队review,测试同时也需要根据产品的埋点需求制定出对应的埋点测试方案。这个阶段主要由产品负责,需要保证需求方案和测试方案都是OK的。
|
||||
</li>
|
||||
<li>
|
||||
**开发阶段**。在开发阶段,开发人员根据产品的埋点需求文档,根据具体的埋点规则在客户端中埋点。开发完成后,需要在本地自测通过。这个阶段由开发负责。
|
||||
</li>
|
||||
<li>
|
||||
**测试阶段**。在测试阶段,测试人员根据产品的埋点需求和规则,通过之前指定的测试方案进行埋点的本地验收。这个阶段由测试负责。
|
||||
</li>
|
||||
<li>
|
||||
**灰度发布阶段**。在灰度发布阶段,测试人员负责对埋点建立线上的监控,查看线上的数据是否符合埋点需求和规则,产品人员需要关心埋点数据是否是符合预期。这个阶段主要是测试负责,但是产品也同样需要参与。
|
||||
</li>
|
||||
|
||||
通过统一埋点流程,我们明确规定了埋点各个阶段的任务与职责,这样可以减少埋点的成本、降低出错的概率。
|
||||
|
||||
**3. 埋点方式**
|
||||
|
||||
代码埋点、可视化埋点、声明式埋点、无痕埋点,对于埋点的方式,业界似乎有非常多的流派。在[《美团点评前端无痕埋点实践》](https://tech.meituan.com/2017/03/02/mt-mobile-analytics-practice.html)和[《网易HubbleData之Android无埋点实践》](https://neyoufan.github.io/2017/07/11/android/%E7%BD%91%E6%98%93HubbleData%E4%B9%8BAndroid%E6%97%A0%E5%9F%8B%E7%82%B9%E5%AE%9E%E8%B7%B5/)都将埋点方式归为以下三类:
|
||||
|
||||
<li>
|
||||
**代码埋点**。在需要埋点的节点调用接口直接上传埋点数据,友盟、百度统计等第三方数据统计服务商大都采用这种方案。
|
||||
</li>
|
||||
<li>
|
||||
**可视化埋点**。通过可视化工具配置采集节点,在前端自动解析配置并上报埋点数据,从而实现所谓的“无痕埋点”, 代表方案有已经开源的[Mixpanel](https://github.com/mixpanel)。
|
||||
</li>
|
||||
<li>
|
||||
**无痕埋点**。它并不是真正不需要埋点,而是自动采集全部事件并上报埋点数据,在后端数据计算时过滤出有用的数据,代表方案有国内的GrowingIO。
|
||||
</li>
|
||||
|
||||
我们平常使用最多的就是“代码埋点”方式,而对于“可视化埋点”和“无痕埋点”,它们都需要实现埋点的自动上报,需要实现事件的自动拦截。
|
||||
|
||||
怎么理解呢?你来回想一下,对于SPM方案中的页面切换事件,我们可以通过监听Activity或者Fragment的切换实现。那怎样自动监听控件的点击和曝光事件呢?
|
||||
|
||||
以监听点击事件为例,一般有下面几种方法:
|
||||
|
||||
<li>
|
||||
**插桩替换**。对于控件的点击,我们可以通过ASM全局将View.onClickListener中的onClick方法覆写成我们自己的Proxy实现,在内部添加埋点代码。
|
||||
</li>
|
||||
<li>
|
||||
**Hook替换**。通过Java反射,从RootView开始,递归遍历所有的控件View对象,并Hook它对应的OnClickListener对象,同样将它替换成我们的Proxy实现。
|
||||
</li>
|
||||
<li>
|
||||
**AccessibilityDelegate机制**。通过AccessibilityDelegate,我们可以检测到控件点击、选中、滑动、文本变化等状态。借助AccessibilityDelegate,当控件触发点击行为时,通过具体的AccessibilityEvent回调添加埋点代码。
|
||||
</li>
|
||||
<li>
|
||||
**dispatchTouchEvent机制**。dispatchTouchEvent方法是系统点击事件的分发函数,通过重写这些函数,就可以实现对所有点击事件的监听。
|
||||
</li>
|
||||
|
||||
## 大数据平台
|
||||
|
||||
虽然我们有了统一的埋点规范和流程,但是整个流程依然是依赖人工手动的。就以埋点需求管理为例,很多团队还在使用Excel来管理,随着不断的修改,文档会越来越复杂,这样也不利于对历史进行跟踪。
|
||||
|
||||
那怎样将埋点管理、埋点开发、埋点测试验证、埋点数据监控打造成一站式的埋点平台呢?
|
||||
|
||||
**1. 埋点一站式平台**
|
||||
|
||||
埋点一站式平台可以实现管理埋点定义的可视化,辅助开发和测试定位埋点相关的问题。并且自动化验证本地和线上的埋点数据,以及自动分析和告警,也可以减少埋点开发和验证的成本,提升数据质量。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/11/ed/111b2b4389c8ae3635d45a954c1aa9ed.png" alt="">
|
||||
|
||||
如上图所示,它主要由四个子平台组成。
|
||||
|
||||
<li>
|
||||
**埋点管理平台**。对应用的整个埋点方案进行统一管理,包括埋点的各个字段的定义和规则,例如对于QQ号这个字段来说,要求是纯数字而且非空的。对于SPM规范,埋点管理平台也会记录每个页面对应的名称,例如淘宝首页会用a123来表示。
|
||||
</li>
|
||||
<li>
|
||||
**埋点开发辅助平台**。开发辅助平台是为了提升开发埋点的效率,例如我前面说到的可视化埋点。或者通过埋点管理平台的字段和规则,自动生成代码,开发者可以一键导入埋点定义的类,只需要在代码中添加调用即可。
|
||||
</li>
|
||||
<li>
|
||||
**埋点验证平台**。验证平台非常非常重要,对于开发人员的埋点测试和测试人员的本地验收,我们可以通过扫码或者其他方式,切换成数据的实时上传模式。埋点验证平台会拉取配置平台的埋点定义和规则,将客户端上报的数据进行实时展示和规则校验。比如说某个埋点漏了一个字段、多了一个字段,又或者是违反了预设的规则,例如QQ号有字母、数值为空等。
|
||||
</li>
|
||||
|
||||
因为手工测试不一定可以覆盖所有的场景,所以我们还需要依赖自动化和灰度验证。它们整体思路还是一致的,只是借助的是线上的非实时通道,每小时或者每日定期输出数据验证的报告。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/83/df/83a4d71e2eaf4f138350178706d97cdf.png" alt="">
|
||||
|
||||
- **埋点监控平台**。监控的目标是保证整个数据链路的健壮性,这里包括对客户端“高可用上报组件”的监控,例如上一期讲到的质量监控和容灾监控。还有对后端数据解析、存储、分析的监控,例如总日志量、日志异常量、日志的丢失量等。
|
||||
|
||||
不知道你是否注意到了,埋点管理平台还会对采样策略进行管理。回到专栏上一期我留给你课后作业的问题,当我们服务器采样策略更新的时候,如果不使用推送,怎样保证新的采样策略可以以最快速度在客户端生效呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/17/e0/17bcae3eb0b202c273b5257bf98d61e0.png" alt="">
|
||||
|
||||
其实非常简单,当用户更改了某个埋点的采样配置时,埋点配置平台会将采样策略版本号自增,然后将最新的策略以及版本号推送到数据采集模块。埋点SDK每次上报都会带上自己本地的策略版本号,如果本地的策略版本号小于服务器的版本号,那么数据采集模块会直接把最新的策略在回包中返回。
|
||||
|
||||
这种方式保证只要客户端有任意一个埋点上报成功,都可以拿到最新的采样策略。事实上,很多其他的配置都是采用类似的方式更新。
|
||||
|
||||
**2. 数据产品**
|
||||
|
||||
埋点的一站式平台,也只是数据平台的一小部分,它负责保证上报数据的准确性。按照我的理解,整个大数据平台的简化架构是下面这个样子的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9a/f5/9a56caba69587db5b89d4ee1dac3b9f5.png" alt="">
|
||||
|
||||
<li>
|
||||
**采集工具层**。应用的上报组件负责数据埋点、日志的组装上报,它需要保证数据的准确性和实时性。
|
||||
</li>
|
||||
<li>
|
||||
**数据采集层**。数据采集层对日志进行清洗、处理,可能还需要和我们的埋点一站式平台进行交互。然后需要根据数据的订阅情况将数据分发到不同的计算模块。
|
||||
</li>
|
||||
<li>
|
||||
**数据计算层**。计算层主要分为离线计算和实时计算两部分,离线计算从数据接收到结果的产出,一般至少需要一个小时以上。而实时计算可以实现秒级、分钟级的计算,一般只会用于核心业务的监控。而且因为计算量的问题,实时计算一般只会计算PV,不会计算UV结果。
|
||||
</li>
|
||||
<li>
|
||||
**数据服务层**。无论是离线计算还是实时计算,我们都会把结果存放到数据的服务层,一般都会使用DB。数据服务层主要是为了屏蔽底层的复杂实现,我们只需要从这里查询最终的计算结果就可以了。
|
||||
</li>
|
||||
<li>
|
||||
**数据产品层**。数据产品一般分为两类,一类是业务型,一类是监控型。业务型一般用来查看和分析业务数据,比如页面的访问、页面的漏斗模型、页面的流向、用户行为路径分析等。监控型主要用来监控业务数据,例如实时的流量监控、或者是非实时的业务数据监控等。
|
||||
</li>
|
||||
|
||||
数据服务层是一个非常好的设计,它让整个公司的人都可以非常简便地实现不同类型的数据产品。我们不需要关心下层复杂的数据采集和计算的实现,只要把数据拿出来,做一个满足自己的报表展示系统就可以了。
|
||||
|
||||
对于实时监控,微信的IDKey、阿里的[Sunfire](https://mp.weixin.qq.com/s/rN2v9SNfyokUL3ijeWwkfg)都是非常强大的系统,它们可以实现客户端数据分钟级甚至秒级的实时PV监控。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/39/3c3885bdef948b216c3c3a7b35c92639.png" alt="">
|
||||
|
||||
对于中小型公司,可能没有能力搭建自己一整套的大数据平台,这个时候可能需要使用第三方的服务,例如阿里云提供了一套[OneData](https://dt.alibaba.com/)服务。
|
||||
|
||||
当然我们也可以搭建一套自己的数据平台,但是对于海量大数据来说,一个稳定、高性能的数据计算层是非常复杂的,我们可以使用外部打包好的数据计算和服务层,例如阿里云的[MaxCompute](https://www.alibabacloud.com/help/zh/doc-detail/27800.htm?spm=a2c63.p38356.b99.2.472d603evPKK4j)大数据计算服务。接着在数据计算层之上,再来实现符合我们自己需求的数据产品。
|
||||
|
||||
下面是一个简单的数据平台整体架构图,你也可以参考大众点评的实现[《UAS:大众点评用户行为系统》](https://zhuanlan.zhihu.com/p/39145535)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/f2/4bbddf641eea9e1e74b56ad7b4e69ff2.png" alt="">
|
||||
|
||||
## 总结
|
||||
|
||||
在过去的几年里,大数据也是一个经常被提起的概念。对于大数据,或者说与之配套的大数据平台,我自己的体会主要有两点。
|
||||
|
||||
**1. 技术变革是为了解决需求**。如果淘宝没有面对每天亿级的用户访问数量,没有一次又一次的被卷入数据的黑洞中,也不会有他们对大数据方面所做的各种艰苦努力,技术是为了解决业务场景中的痛点。另一方面看,大数据的确存在门槛,如果在中小型企业可能不一定有这样锻炼的机会。
|
||||
|
||||
**2. 基础设施建设没有捷径可走**。高可用的上报组件、埋点一站式平台以及各种各样的数据产品,这些基础设施的建设需要有足够的耐心,投入足够的人力、物力。为什么要采用这样的规范和流程?为什么架构会这样设计?虽然这些方案可能不是最优的,但也是通过血与泪、通过大量的实践,慢慢演进得来的。
|
||||
|
||||
## 课后作业
|
||||
|
||||
你所在的公司,有没有统一的埋点规范和埋点流程?对于数据相关的配套设施建设得怎么样?在数据保障方面遇到了哪些问题?欢迎留言跟我和其他同学一起讨论。
|
||||
|
||||
在数据平台的建设上面,国际的Facebook、国内的阿里都是做得非常不错的公司,我推荐你看看阿里数据专家们写的一本书《大数据之路 阿里巴巴大数据实践》。
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
187
极客时间专栏/Android开发高手课/模块二 高效开发/32 | 线上疑难问题该如何排查和跟踪?.md
Normal file
187
极客时间专栏/Android开发高手课/模块二 高效开发/32 | 线上疑难问题该如何排查和跟踪?.md
Normal file
@@ -0,0 +1,187 @@
|
||||
<audio id="audio" title="32 | 线上疑难问题该如何排查和跟踪?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b1/82/b125c44acae518d1a340bd75a6139f82.mp3"></audio>
|
||||
|
||||
“95%以上的崩溃都能解决或者规避,大部分的系统崩溃也是如此”,这是我在专栏“崩溃优化”中曾经夸下的海口。
|
||||
|
||||
虽然收集了尽可能丰富的崩溃现场,但总会有一些情况是事先没有预料到的,我们无法直接从崩溃日志里找到原因。事实上我们面临的难题远远不止崩溃,比如说用户投诉文件下载到99%之后无法继续,那如何确定是用户手机网络不好,是后台服务器出错,还是客户端代码的Bug?
|
||||
|
||||
我们的业务逻辑越来越复杂,应用运行的环境也变得越来越复杂,因此在实际工作中总会遇到大大小小的线上疑难问题。对于这些问题,如何将它们“抽丝剥茧”,有哪些武器可以帮助我们更好地排查和跟踪呢?
|
||||
|
||||
## 用户日志
|
||||
|
||||
对于疑难问题,我们可以把它们分为崩溃和非崩溃两类。一般有哪些传统的排查手段呢?
|
||||
|
||||
<li>
|
||||
**本地尝试复现**。无论是崩溃还是功能性的问题,只要有稳定的复现路径,我们都可以在本地采用各种各样的手段或工具进行反复分析。但是真正的疑难问题往往都很难复现,它们可能跟用户机型、本地存储数据等环境有关。
|
||||
</li>
|
||||
<li>
|
||||
**发临时包或者灰度包**。如果发临时包给用户,整个过程用户配合繁琐,而且解决问题的时间也会很长。很多时候我们根本无法联系到用户,这个时候只能通过发线上灰度包的方式。但是为了一步步缩小问题的范围,我们可能又需要一次次地灰度。
|
||||
</li>
|
||||
|
||||
我们多么希望能有一些“武器”,帮助工程师用非常低的成本,在非常短的时间内,尽可能地收集足够丰富的信息,更快速地排查和解决问题。
|
||||
|
||||
**1. Xlog**
|
||||
|
||||
在日常开发过程中,我们经常会使用Logcat日志来排查定位代码中的问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/f7/1dc8e743989e6e45c5045d18e54172f7.png" alt="">
|
||||
|
||||
对于线上问题,我们也希望可以有用户的完整日志,这样即使问题不能复现,通过日志也可能定位到具体的原因。所谓“养兵千日,用兵一时”,客户端日志只有当出现问题且不容易复现时才会体现出它的重要作用。但是为了保证关键时刻有日志可用,就需要保证程序整个生命周期内都要打日志,所以日志方案的选择至关重要。
|
||||
|
||||
在过去因为性能和可靠性问题,通常我们只针对某少部分人动态打开日志开关。那如何实现一套高性能、日志不会丢失并且安全的日志方案呢?微信在2014年就实现了自己的[高性能日志模块Xlog](https://mp.weixin.qq.com/s/cnhuEodJGIbdodh0IxNeXQ?),并且在2016年作为Mars的一部分开源到[GitHub](https://github.com/Tencent/mars)。关于Xlog的更多实现细节,你可以参考[源码](https://github.com/Tencent/mars/tree/master/mars/log)或者[会议分享](https://github.com/AndroidAdvanceWithGeektime/Chapter32/blob/master/%20mars%20%E9%AB%98%E6%80%A7%E8%83%BD%E6%97%A5%E5%BF%97%E6%A8%A1%E5%9D%97%20xlog.pdf)。
|
||||
|
||||
Xlog方案的出现,可以让全量用户全天候打开日志,也不需要担心对应用性能造成太大的影响。但是Xlog只是一个高性能的日志工具,最终是否能解决我们的线上问题,还需要看我们如何去使用它。
|
||||
|
||||
所以微信制定了严格的日志规范,定期对拉取的日志作规则检查,一旦发现有违反规则的情况,会作出一定的处罚。下面是其中的一些日志规范,我选取一些分享给你。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4a/e2/4a088cebbd440d6668f9b4609e5ca3e2.jpg" alt="">
|
||||
|
||||
日志打点怕打太多也怕太少,担心出现问题没有足够丰富的信息去定位分析问题。应该打多少日志,如何去打日志并没有一个非常严格的准则,这需要整个团队在长期实践中慢慢去摸索。在最开始的时候,可能大家都不重视也不愿意去增加关键代码的日志,但是当我们通过日志平台解决了一些疑难问题以后,团队内部的成功案例越来越多的时候,这种习惯也就慢慢建立起来了。
|
||||
|
||||
**2. Logan**
|
||||
|
||||
对于移动应用来说,我们可能会有各种各样的日志类型,例如代码日志、崩溃日志、埋点日志、用户行为日志等。由于不同类型的日志都有自己的特点,这样会导致日志比较分散,比如我们要查一个问题,需要在各个不同的日志平台查不同的日志。美团为了解决这个问题,提出了统一日志平台的思路,也在[Github开源](https://github.com/Meituan-Dianping/Logan)了自己的[移动端基础日志库Logan](https://tech.meituan.com/2018/02/11/logan.html)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f7/f8/f70cc52f50ff6d8c954edbc8e4c82df8.png" alt="">
|
||||
|
||||
Logan整合了各式各样的日志平台,打造成一个统一的日志平台,进一步提升了开发人员查找问题的效率。不过无论是Logan还是Xlog,日志一般会通过下面两种方式上报。
|
||||
|
||||
- **Push拉取**。通过推送命令,只拉取特定用户的日志。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b0/8b/b0b26349e3ab3c0bd839e21495c2778b.png" alt="">
|
||||
|
||||
- **主动上报**。在用户反馈问题、出现崩溃等一些预设场景,主动上报日志。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/40/70/405f8fe3e8a368c3359cf52a9ffd6b70.png" alt="">
|
||||
|
||||
对于用户日志,我们是否已经做到尽善尽美了?手动埋点的覆盖范围有限,如果关键位置没有预先埋点,那可能就需要重新发包。所以美团在Logan基础上,还推出了[Android动态日志系统Holmes](https://tech.meituan.com/android_holmes.html)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/b6/f315e4532915e63d10ea8fc1bec942b6.png" alt="">
|
||||
|
||||
Holmes的实现跟美团的Robust热修复思路差不多,需要对每个方法进行了插桩来记录方法执行路径,也就是在方法的开头插入一段桩代码,当方法运行的时候就会记录方法签名、进程、线程、时间等形成一条完整的执行信息。但是这套方案性能的技术难点比较多,一般只会动态针对出现问题的用户开启。
|
||||
|
||||
虽然这个思路有一定的启发性,但我认为其实并不太实用。一来,对每个方法插桩,会对安装包体积和性能造成很大的影响,导致这套方案过于笨重;二来,很多疑难问题都具有偶发性,当用户出现问题后,再去打开用户日志,可能也不能保证用户的问题可以复现。
|
||||
|
||||
## 动态调试
|
||||
|
||||
“只要你能在本地复现,我就能解决”,这可能是开发对测试说过最多的话了。在本地,我们可以通过增加日志,或者使用Debugger、GDB等这样的调试工具反复进行验证。
|
||||
|
||||
针对远程用户,如果我们可以做到具备跟本地一样的动态调试能力,想想都觉得激动人心。那有没有方案能实现远程动态调试呢?
|
||||
|
||||
**1. 远程调试**
|
||||
|
||||
动态调试,又或者是动态跟踪,它们属于高级的调试技术。事实上,它并不是什么新鲜的话题,例如Linux中大名鼎鼎的DTrace和SystemTap、Java的BTrace,都是非常成熟的方案。我推荐你仔细阅读[《动态调试漫谈》](https://openresty.org/posts/dynamic-tracing/)和[《Java动态追踪技术探究》](https://tech.meituan.com/2019/02/28/java-dynamic-trace.html),特别是前者,让我有非常大的收获。
|
||||
|
||||
在Android端,我们能不能实现对用户做动态调试呢?在回答这个问题之前,请先来思考一下平时我们通过Android Studio进行调试的底层原理是什么。
|
||||
|
||||
其实我们的学习委员鹏飞之前已经讲过这块内容了,回到“[Android JVM TI机制详解](https://time.geekbang.org/column/article/74484)”中说到的Debugger Architecture。Java的调试框架是通过JPDA(Java Platform Debugger Architecture,Java平台调试体系结构),它定义了一套独立且完整的调试体系,主要由以下三部分组成:
|
||||
|
||||
- [JVM TI](https://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#whatIs):Java虚拟机工具接口(被调试者)。
|
||||
- [JDWP](http://download.oracle.com/otn_hosted_doc/jdeveloper/904preview/jdk14doc/docs/guide/jpda/jdwp-spec.html):Java调试协议(通道)。
|
||||
- [JDI](http://download.oracle.com/otn_hosted_doc/jdeveloper/904preview/jdk14doc/docs/guide/jpda/jdi/index.html):Java调试接口(调试者)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f0/70/f043f50f8e6e99d627604b867e767a70.jpg" alt="">
|
||||
|
||||
如果你想了解更多关于Java调试框架的信息,可以重新回顾一下“[Android JVM TI机制详解](https://time.geekbang.org/column/article/74484)”里给出的参考链接。
|
||||
|
||||
对于Android来说,它的调试框架也是在Java调试框架基础上进行的扩展,主要包括Android Studio(JDI)、ddmlib、adb server、adb daemon、Android应用这五个组成部分。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/76/f34e7b16e572fe4f1a4d33b96681cd76.png" alt="">
|
||||
|
||||
如果想要实现对用户的远程调试,我们需要修改其中的两个部分。
|
||||
|
||||
<li>
|
||||
**JDWP(传输通道)**。我们不能使用系统adb的方式,而是希望将用户的调试信息经过网络通道发送给我们。
|
||||
</li>
|
||||
<li>
|
||||
**JDI(前端展示)**。对于客户端调试数据的展示,我们不太容易直接复用Android Studio,需要重新实现自己的数据展示页面。
|
||||
</li>
|
||||
|
||||
关于具体实现的细节,你可以参考美团的文章[《Android远程调试的探索与实现》](https://tech.meituan.com/2017/07/20/android-remote-debug.html),最终整体的流程如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/34/35/34176437ad0313c5dd057cc75f2c7b35.png" alt="">
|
||||
|
||||
当然不同于本地Debug包的调试,对于用户调试我们还需要考虑如何突破ProGuard和Debugable的影响。总的来说,这套方案有非常大的技术价值,可以加深我们对Java调试框架的理解。但是它并不实用,因为大部分的场景,我们很难在用户不配合的前提下做好调试。而且调试过程也可能会出现各种各样的情况,并不容易控制。
|
||||
|
||||
不过退而求其次,通过这个思路,我们可以在本地实现“无线调试”(无需adb),又或者是实现对混淆包的调试。
|
||||
|
||||
**2. 动态部署**
|
||||
|
||||
如果说远程调试并不很实用,那有没有其他方法让用户感知不到我们在进行调试呢?
|
||||
|
||||
用户无感知、代码更新,这不正是动态部署所具备的能力,而且动态部署天生就非常适合使用在疑难问题的排查上。
|
||||
|
||||
<li>
|
||||
**精细化**。通过发布平台,我们可以只选择某些问题用户做动态更新。也可以圈定某一批用户,例如某个问题只在华为的某款机型出现,那我可以只针对华为的这款机型下发。
|
||||
</li>
|
||||
<li>
|
||||
**场景**。对于疑难问题的排查,我们一般只会增加日志或者简单修改逻辑,动态部署在这个场景是完全可以满足的。
|
||||
</li>
|
||||
<li>
|
||||
**可重复、可回退**。对于疑难问题,我们可能需要反复尝试不同的解决思路,动态部署完全可以解决这个需求。而且在问题解决后,我们可以及时将无用的Patch回退。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/73/db/736d7f69fd81c92857ef1971cae83ddb.jpg" alt="">
|
||||
|
||||
我还记得曾经为了解决libhw.so的崩溃问题,我们历经一个月,一共发布了30多个动态部署,反复地增加日志、增加Hook点,最终才得以解决。
|
||||
|
||||
**3. 远程控制**
|
||||
|
||||
动态部署存在生效时间比较慢(几分钟到十几分钟)、无法覆盖100%用户(修改AndroidManifest或者用户手机没有剩余空间)等问题。对于一些特定问题,我们可以通过下发预设规则的方式来处理。
|
||||
|
||||
网络远程诊断是一个非常经典的例子,假如有个用户反馈某个网页无法打开,我们可以通过本地或者远程下发指令方式,对用户的整个网络请求过程做完整的检测,看看是不是用户的网络问题,是不是DNS的问题,在请求的哪个阶段出错了,错误码是什么等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9b/65/9baa7684679f40926ea93bbbb4a44865.png" alt="">
|
||||
|
||||
Mars里面也有一个专门的SDT网络诊断模块,我们可以顺便回顾一下Mars整个知识结构图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/51/60/51cf08e8fe7d78d7c485587da32b0460.png" alt="">
|
||||
|
||||
除了网络的远程诊断之外,网络疑难问题的排查和跟踪本身就是一个非常大的话题。它涉及业务请求从域名解析/流量调度到业务统一接入,再到业务调用的整个访问链路,属于大网络平台的一环。
|
||||
|
||||
我们可以通过客户端生成的traceId,将统一收集和整合客户端日志、服务端调用日志、自建CDN等日志,建立以用户为维度的监控平台,提供问题定位功能 。例如Google的Dapper、阿里的EagleEye、微信的点击流平台、QQ的全链路监控平台等,都是通过这个思路实现的。
|
||||
|
||||
类似网络远程诊断,又或者是删除某些文件、上报某些信息,这些预设规则是建立在我们已经踩过某个坑,或者更多情况是已经无数次踩到同一个坑,并且忍无可忍,才会搭建一套相应的诊断规则。那我们能不能不通过动态部署,也可以简单的调用某些Java代码呢?
|
||||
|
||||
这个时候就不得不提到非常强大的Lua脚本语言,iOS之前大名鼎鼎的[Wax热修复](https://github.com/alibaba/wax)、[腾讯Unity3D的热更新方案](https://infoq.cn/article/2017/01/C-NET-Lua-Unity3D),都是使用Lua来实现。Lua的VM非常小,只有200KB不到,充分保证了时间和内存开销的可控。我们可以对目标用户下发指令,动态地执行一段代码并将结果上报,或者在方法运行的时候去获取某些对象、参数的快照信息。
|
||||
|
||||
下面是Lua和Android的调用事例。
|
||||
|
||||
```
|
||||
// lua脚本函数
|
||||
function setText(textView)
|
||||
tv:setText("set by Lua."..s); //这里的s变量由java注入
|
||||
tv:setTextSize(30);
|
||||
end
|
||||
|
||||
// android调用
|
||||
lua.pushString( "from java" ); //压入欲注入变量的值
|
||||
lua.setGlobal( "s" ); //压入变量名
|
||||
lua.getGlobal( "setText" ); //获取Lua函数
|
||||
lua.pushJavaObject( textView ); //压入参数
|
||||
lua.pcall( 1, 0, 0 ); //执行函数
|
||||
|
||||
```
|
||||
|
||||
对于Lua的使用,你可以参考[官方文档](https://www.lua.org/manual/5.3/)。为了方便我们在Android更加容易的使用Lua,也有不少开源库对Lua做了更好的封装,例如[AndroLua](https://github.com/mkottman/AndroLua),阿里也有一套基于Lua实现的动态化界面框架[LuaViewSDK](https://github.com/alibaba/LuaViewSDK)。
|
||||
|
||||
美团的Holmes也利用Lua增加了DB查询、上报普通文本、ShardPreferences查询、获取Context对象、查询权限、追加埋点到本地、上传文件等综合能力。正因为Lua脚本如此强大,很多大厂App也都在Android中集成Lua。
|
||||
|
||||
## 总结
|
||||
|
||||
对于美团、支付宝、淘宝这些超级应用来说,不同的平台、不同的业务可能有上千人同时在一个应用上面开发协作。业务量大、多地区协作开发、业务类型多,每当出现问题都会感到耗时耗力,心力交瘁。
|
||||
|
||||
正因为反复“痛过”,才会有了微信的用户日志和点击流平台,才会有美团的Logan和Homles统一日志系统。所谓团队的“提质增效”,就是寻找团队中这些痛点,思考如何去改进。无论是流程的自动化,还是开发新的工具、新的平台,都是朝着这个目标前进。
|
||||
|
||||
## 课后作业
|
||||
|
||||
在你的工作中,遇到或者解决过哪些经典的疑难问题?还有哪些强大的疑难问题的排查武器?欢迎留言分享给我和其他同学。
|
||||
|
||||
无论是推送拉取用户日志,还是远程调试命令的下发,我们都需要具备区分用户的能力。对于微信这样强登录的应用,我们可以使用微信号作为用户标识。但是用户登陆之前的日志该如何收集呢?
|
||||
|
||||
对于用户唯一标识,Google有自己的[最佳实践方案](https://developer.android.com/training/articles/user-data-ids?hl=zh-cn)。对于大部分非强登录的应用,搭建自己的用户标识体系非常重要。用户唯一标识需要考虑漂移率、碰撞率以及是否跨应用等因素,业界常用的方案有阿里的UTDID、腾讯MTA ID。
|
||||
|
||||
今天的课后作业是,应用该如何实现自己的用户标识体系,请你在留言中写下自己的答案。
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。最后别忘了在评论区提交今天的作业,我也为认真完成作业的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
|
||||
|
||||
|
||||
93
极客时间专栏/Android开发高手课/模块二 高效开发/33 | 做一名有高度的移动开发工程师.md
Normal file
93
极客时间专栏/Android开发高手课/模块二 高效开发/33 | 做一名有高度的移动开发工程师.md
Normal file
@@ -0,0 +1,93 @@
|
||||
<audio id="audio" title="33 | 做一名有高度的移动开发工程师" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/13/ce/1365b92bcc5c290f636b03201572f6ce.mp3"></audio>
|
||||
|
||||
专栏更新至今,不知不觉第二模块“高效开发”也已经更新完了。稳定性、内存、卡顿、I/O、网络,“高质量开发”模块打通了从应用层、Android系统层、Linux内核层再到硬件层的优化路径,帮助我们打通“任督二脉”,成为一名Android开发高手。
|
||||
|
||||
所谓“高效开发”,可以给我们带来了什么呢?移动互联网发展到今天,所有人都说“提质增效”,但是团队效能不是靠我们封装一个工具类或者组件,给其他人低成本复用就够了。持续交付平台、测试平台、发布平台、数据平台、网络平台…我希望你可以跳出客户端的限制,去思考整个产品的研发流程有哪些痛点,不同团队的协作有哪些优化空间,尝试去提升产品的质量和团队的效率。
|
||||
|
||||
我们需要的是多想一步,哪怕只是多思考一小步,对自身的成长可能就价值巨大。想要成为一名全面的“开发高手”,不仅要具备系统性解决应用性能和架构问题的攻坚能力,也要有从全局俯视体系和流程的思维能力。这就是我在“高效开发”里希望带给你的思考,希望你可以成为一名“站在高处”的移动开发工程师。
|
||||
|
||||
## 成为有高度的移动开发工程师
|
||||
|
||||
在微信的时候,我非常推崇T型技术人才理论,所谓的“T”无非就是横向和纵向两个维度。纵向解决的是深度问题,横向解决的是广度问题。
|
||||
|
||||
一个有高度的移动开发工程师,需要能纵向深入,也要能横向全面地思考每一个问题。比如说团队希望治理数据的准确性和实时性问题,如果站在客户端的角度上看,就是思考如何去实现一套数据不会丢失、实时性高以及高性能的埋点上报组件。我们知道,这里面的进程模式、存储模型、同步机制等都很复杂,要做一个高可用的上报组件确实需要具备一定的技术深度。
|
||||
|
||||
但是如果站在更高的角度上看,你会发现上报组件的优化并不能从根本上解决团队的数据问题。埋点的规范是什么?埋点的流程是什么?产品、研发、数据、测试几个团队对于数据有哪些痛点?我们需要梳理一个埋点从产品定义、客户端埋点开发、测试验证、后端数据处理、数据展示和监控的整个过程。针对团队的数据治理,我们需要体系化的思考每一个点的问题,从更高的角度去全局思考。
|
||||
|
||||
那应该如何来提升自己的高度,站在高处去思考问题呢?下面是我的一些思考。
|
||||
|
||||
**1. 从终端到跨端**
|
||||
|
||||
在App开发最原始的时代,为了实现代码的内部复用,我们封装了各种各样的Util工具类。随着移动互联网的发展,应用越来越多也越来越复杂,代码还需要在不同应用中复用。这个时候客户端组件呈现出爆发式增长,例如图片库中的Glide、Fresco、Picasso,组件化中Atlas、DroidPlugin、RePlugin等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0e/dd/0eb4f07e4d28a518081b2f6ea97ddfdd.png" alt="">
|
||||
|
||||
回想一下,因为当时Android系统的不成熟和不完善,反而造就了一个百花齐放的移动开发时代。在这个时代里,我们总可以找到很多优化的点,并且持续打磨。随着应用业务复杂性和要求的提升,单纯在客户端的单点优化已经满足不了业务的诉求了,比如在直播、小程序这样的复杂场景。
|
||||
|
||||
这个时候我们第一步就要跳出自身客户端的角色限制,从更为全局的角度看问题、思考问题。你需要明白,客户端的实现只是其中一小块内容而已。假如你接到一个提升页面打开速度的任务,极致优化的基础是我们能深入研究浏览器的渲染原理和缓存机制,但是前端和后端能够做些什么,又应该做些什么呢?除此之外,页面哪里产生、如何发布、发布到哪里、如何下载、如何解析、如何渲染、如何衡量和监控页面的性能,这些全部都是我们需要思考的问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/55/6d/551c6483089126b7acdf3c4a322ca66d.png" alt="">
|
||||
|
||||
一方面,在你的项目还没有证明它的价值之前,可能很多公司并不愿意投入很多的人力。这个时候我们只能去包办前后端,就像当年微信的日志平台、APM平台,记得还是我们用Tornado先简单搭建起来。等到这个项目证明了它的价值,才会拉更多的人参与进来。所以这就需要我们具备跨端的能力,目标是解决产品的问题,要知道客户端技术并不是唯一的选择。
|
||||
|
||||
另外一方面,针对持续交付平台、测试平台、网络平台、数据平台,很多平台客户端开发者本来就是使用方,我们应该更清楚里面的痛点是什么,有哪些可以改进的地方,所以客户端开发者应该更能主导这些平台的演进。
|
||||
|
||||
**2. 从平台到中台**
|
||||
|
||||
正如我上面说到的,组件化只是客户端技术最基本的抽象的体现。怎么理解呢?以性能组件为例,虽然我们收集了应用各个维度的性能数据,但是这些数据在后台如何聚合、如何存储、如何分析、如何报警,我们并没提供解决方案。
|
||||
|
||||
每个接入的应用还是要花很大的力气去搭建一整套系统,为了解决这个问题,集成式服务化的建设开始出现,比如以Google的Firebase为代表的各个开发者平台。为了解决应用不同的场景,我们不断地孵化出不同的服务平台。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/69/e3/698de0e867d4139d8db8b90864b297e3.png" alt="">
|
||||
|
||||
移动开发早就已经过了单兵作战的年代了,客户端单点的深耕细作已经不是唯一考量的因素。有没有配套的服务、服务是不是简单易用,这些因素对于开发者来说越来越重要。特别是对于大厂来说,一个公司有几十上百个应用,对于公共业务需要避免重复劳动。国内蚂蚁的mPaaS、阿里云的EMAS移动开发者平台,都是遵循这样的服务化思路。
|
||||
|
||||
但是平台化是不是就是服务的最终形态呢?你有没有体验过这种的痛苦:一个新应用需要接入公司内部十几个不同的平台,它们的账号信息、注册信息都相互独立,很多功能我们还需要单独去跟每个平台联调测试。为了解决各个平台的割裂,在平台化的基础上,又提出了中台化的思路。
|
||||
|
||||
什么是中台呢?简单的理解就是把这些分散的平台又统一为一个超大的平台。有人会想我们是不是在开历史的倒车?还记得当年我们将一个庞大的系统分拆成各个子平台是多么的艰难。事实上,这里中台的“统一”,更多是面向开发者层面的,例如都使用同一个账号、不需要重复注册、平台之间互相闭环等。
|
||||
|
||||
关于中台的更多资料,你可以参考[《从平台到中台【上】》](https://mp.weixin.qq.com/s/dpkteHsQJ4Rwl6YNl2PVeg?)和[《从平台到中台【下】》](https://mp.weixin.qq.com/s/TirTQfWo0gX9PUw_okdGjQ?)。在国内,阿里的中台是做得最好的。当然腾讯、头条这些公司也都意识到了它的重要性,最近都在积极调整组织架构,成立了专门的中台部门。但是无论是中台还是平台,都是靠无数大大小小的优化点堆积起来得,它们都需要慢慢地积累,很难在非常短的时间内建设得非常完善。
|
||||
|
||||
## 热点问题答疑
|
||||
|
||||
针对高效开发,我全面介绍了目前各大公司的做法和思路。对于持续交付、测试、发布、数据、网络、日志,它们本身涉及的知识是十分庞大的,所以就正如高质量开发一样,即使专栏的内容不能完全理解,也大可不必焦虑,可以反复多读几次专栏的文章和扩展的参考资料,相信你每次学习都会有不同的收获。
|
||||
|
||||
你可以按照自己的节奏持续地学习下去,这样我相信无论是对你的视野还是对移动开发的理解,都会有很大的收获。但是在向提升高度的方向迈进时,我们或多或少都会有一些疑问,下面我就挑选几个比较重要的问题,和你进一步聊聊我的看法。
|
||||
|
||||
**1. 如何提升个人的专注力和效率?**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/43/77a9b2fc0b835ed98aea0783967e1843.png" alt="">
|
||||
|
||||
人的大脑就像CPU一样,如果频繁切换进程和线程,这个代价是非常大的。一会看一下微信,一会刷一下抖音,一会看一下头条,当我们重新切换回工作线程的时候,起码需要几分钟才能重新进入状态。
|
||||
|
||||
那靠个人的自制力能不能解决这个问题?能,但是非常遗憾的是大部分人都没有这个能力,或者说自制力不够强大。不要给自己被诱惑的机会,因为大部分人都无法承受诱惑。我的建议是,直接卸载掉这些可能影响我们工作的软件(微信可能卸载不了,这也是它强大的地方,笑)。
|
||||
|
||||
**2.个人发展与公司平台的关系**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/24/b8/240f82a329021fd4e8161bf9b07468b8.png" alt="">
|
||||
|
||||
可能DebugCat同学的疑问你也会有共鸣,我每天的工作就是写写界面、调调动画,面对的都是无休无止的业务,你讲的这些东西虽然高大上,但是并没有机会接触到。
|
||||
|
||||
对于小型团队来说,更多的是拿来主义,去使用一些第三方的平台。对于大型团队来说,可能你才有机会真正参与到这些平台的开发。但是也有人说在小型团队可以独当一面,但在大厂只能做一颗小小的螺丝钉。
|
||||
|
||||
我相信业务和团队这些限制因素的确客观存在,而且对我们的影响的确巨大。但是这并不是决定性的因素,你要问问自己有没有真的去努力。
|
||||
|
||||
如果你在大厂,就应该从客户端到后端,尽可能全面深入研究你参与的模块,多想想如何把你所做的模块优化到极致,并且在巨大的用户量面前依然能够稳定运行。如果你在初创团队,在业余时间也要坚持学习,持续探索自己的技术深度。这样在将来,无论是初创团队内部的晋升,还是跳到大厂,这样努力的经验都可以成为未来无数次面试、加薪的一大亮点。
|
||||
|
||||
## 总结
|
||||
|
||||
移动开发工程师想要真正站在高处,既需要有技术深度,又要有广度。很明显你下一个问题是:应该先钻研深度,还是扩展广度呢?
|
||||
|
||||
我建议你应该至少先在一个技术领域付出大量的精力,深入钻研透彻,然后再去思考广度的问题。这是因为经验丰富的程序员学新的东西都非常快,因为现在已经不那么容易出现太多全新的技术,所谓的新技术其实都是旧技术的重新组合和微创新。
|
||||
|
||||
成长是没有捷径的,我发现技术圈也有部分人喜欢在论坛写写文章或者出去授课,在业界可能还小有名气,受到不少人追捧。但是只要真正去大厂面试,可能就会被打回原形。我推荐你看看[这篇文章](https://mp.weixin.qq.com/s/iYb7itFve629ODHuzFH-5g),这里把里面的一句话推荐给你:
|
||||
|
||||
>
|
||||
老老实实看书,踏踏实实做事儿,早日兑现自己曾经吹过的牛逼。
|
||||
|
||||
|
||||
“金三银四”,最近也是找工作的高峰期。从很多同学的面试经历来看,现在只会单纯写业务代码的人找工作特别难,比如很多大厂的面试官都会针对性能优化的细节,考察你是否真正搞懂底层的机制和原理。环境的要求越来越高,所以我们也要积极转变,踏踏实实的学习。最后我也推荐你看看[《程序员成长路线》](https://mp.weixin.qq.com/s/nUtUu6e_bXHvb_06Pf_05g),希望今天讲的这些“大道理”对你有所启发。
|
||||
|
||||
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user