移动开发:罗曼蒂克的消逝之旅

发表时间: 2019-02-13 15:52

作者 | 徐川

编辑 | 小智

写在前面

今天,我想给大家讲一段故事,这个故事里包含有黑科技、天才少年,有意气风发的豪情和壮志未酬的遗憾。更重要的是,这是一段真实的故事,是我人生中的一段重要经历。


对于互联网人来说,这个冬天格外寒冷。

“辛苦一年半,现在要被联合创始人给踢出局了,技术创业真是悲哀。”一个沉寂已久的微信群里,突然冒出了这样一句话。

我看了一眼发消息的人,备注是“勇哥 创业”,我心里一紧。

群友被这个消息炸出来,有的看热闹,有的义愤填膺,纷纷要求曝光无良公司。我却不由得回想起当初与勇哥结识的故事。

勇哥大名叫张勇,我与他相识是在 2015 年秋季,当时我正在一个程序员论坛上闲逛,突然一个帖子映入我的眼帘:“搞了个安卓上免安装运行的,准备开源一下”。这个帖子一下子就引起了我的兴趣。

移动开发正是我当时的关注领域,我对智能手机上一切前沿的、好玩的技术都充满好奇。帖子里说,他的这个技术可以通过主 App 启动任意第三方 App,我以前从未听说过这样的技术。

很快,通过朋友介绍,我和帖子的作者张勇搭上了线,他当时是安卓版 360 手机助手的技术负责人,9 月份,我前往酒仙桥 360 总部,与他见了面。

张勇敦厚面相中透着机灵,和大部分程序员不一样,他十分健谈,说起自己开发的 DroidPlugin 眼里带着光。和他聊了两个小时,我确信,这是一项安卓开发黑科技。

安卓黑科技

中国的技术都是业务驱动的,先有需求,然后研究怎么能做到,DroidPlugin 诞生的背景也是如此。

14 年左右,中国和国外的 App 理念走上了截然不同的两条道路。在国外,一个 App 最多做两三件事,但在中国,一个 App 恨不得装下所有功能,这就是所谓的超级 App。

超级 App 有很多好处,但是,谷歌在设计安卓的时候,没有考虑到存在超级 App 的情况,在安卓早期版本里,一个 App 里只允许存在 65536 个方法,一旦超过就会报错。65536 已经很大了,就和千年虫问题一样,开发安卓的工程师们根本没想到有 App 会需要那么多的方法。

这个问题在国外被 Facebook 发现了,Facebook 的 App 很大,可能是国外仅有的几个可以称作超级 App 的应用了,它给出了一个暂时绕过的解决办法。

国内开发者不满足这种暂时绕过的方法,早在 2012 年,大众点评的工程师图毅敏在研究 Android 源码的过程中就发现,通过对 AndroidDynamicLoader 方法的应用,可以做到动态加载资源甚至代码。2014 年底,当时在百度的安卓工程师任玉刚开源了 Dynamic-load-apk,将这种方法更进一步。

到张勇这里,他把动态代理发展到了极致,让 Android 系统的四大组件都可以动态加载,这样,安装 App 的时候只用装一个宿主 App 或者叫壳 App,然后在宿主 App 里远程下载代表各个功能模块的 App 就行了。这种技术流派,后来被称为安卓插件化技术。

想象一下,你的手机只用安装一个 App,如果想用其它 App,点击下载之后就可以运行,省去了麻烦的安装过程,甚至你还可以用不同的账号同时打开一个 App,这就是插件化的神奇之处。

安卓插件化的一个变种是组件化技术,它并不用分成不同的 App,而是平时各个模块分开开发,发布的时候一起打包,这种技术的集大成者就是手机淘宝研发的 Atlas 组件化框架,2014 年初伯奎对外首次分享,2017 年 3 月正式开源。

超级 App 还会带来一个问题,就是 App 的更新,当 App 由数十个团队,数百上千人开发,版本控制和更新变成了一个很麻烦的事情,特别是线上版本发现 bug 时需要及时更新版本处理,而国内安卓渠道众多,依赖各个平台更新是不现实的,只有自己处理更新,为了降低更新给用户带来的影响,国内又发明了热更新技术。

早期的热更新技术借鉴了安卓极客最爱的工具 Xposed,2015 年 7 月左右,淘宝的白衣开源了安卓切面编程框架 Dexposed,它在 Xposed 的基础上进行改造,使其不需要 root 就可以任意改变应用的功能。但是,这个技术只支持 Dalvik 运行时,对于新的 ART 运行时无能为力。随后不久,支付宝安卓团队推出了 AndFix,能很好的支持 ART,很快成为阿里系的标准热更新工具。

腾讯这边,QQ 空间在 2015 年分享了它的热补丁技术,它是基于谷歌为了解决方法数溢出而推出的 MultiDex 方案,第二年微信推出了更好的热更新框架 Tinker,也被广大的开发者所喜爱。

2016 年,美团的人告诉我,他们在研究了 Android Studio 2.0 里的 Instant Run 功能后,推出了 Robust 热更新框架,成为安卓热更新的一个新的技术流派。

插件化、组件化、热更新,从 2015 年开始,国内的移动开发技术爆发了井喷式的发展,这些是国内独有的技术,在这一期间涌现出无数开发者,他们抱着极大的热情研究技术并进行开源和分享,那是一个对移动开发者最好的时代。

在这一群人中,我有两个人印象比较深刻。

天才少年

2016 年初,我开始筹办 GMTC 全球移动技术大会,找张勇推荐讲师,他给我介绍了罗迪。从勇哥的描述里,显然他对罗迪的技术极为认同,然而让我大吃一惊的是,罗迪当时才高二,还是个在校学生。

我一度怀疑他的技术水平,不过在通过邮件和微信交流后,我打消了这种怀疑。他当时已经开源了几个 Android 插件化方面的工具,其中包括 ART 运行时的 Hook 工具 Legend,这是当时最前沿的课题。在交流中我也了解到,他对插件化技术发展有深入的洞察,让我十分佩服。

毫无疑问这是一个天才少年。据他自述,他从初二开始自学编程,初三开始学 Java 和安卓开发,业余时间全部用来学习和研究 Android 源码,这一点,就连职业的开发者也难以做到。他的天才并不是说具有学习和编程的天赋,而是可以静下心来学习在一般人看起来枯燥的技术。

不过,虽然我认同他的技术,但是在权衡之后我还是放弃了让他当讲师的想法,因为不想拔苗助长。但我邀请他来参加 GMTC 大会,以及一个安卓的闭门会议,并在闭门会议上做一个分享。

6 月 24 号,GMTC 如期举行,我也见到了罗迪,他在微信上很活跃,但在现实中看上去比较木讷,讲一句话需要思考一段时间,不太擅长与人打交道。

他说,插件化今后的方向是沙盒和双开,后者又被称为“分身”,曾有一段时间,各种手机游戏小号、微信分身非常火,就是用的这种技术。

值得一提的是,当年那次闭门会议,几乎囊括了当时在安卓插件化方面研究最前沿的一批人,会议结束后,我请他们吃饭,拍照留念,现在一看,全是回忆。


(GMTC2016 安卓插件化闭门会议合影,后排右二是罗迪)

当时的罗迪已经被市场所发现了,我发布了采访他的文章后,有人专门给我写邮件想让我把罗迪介绍给他,张勇还告诉我有老板专门到北京就是为了看他。

再后来我没有他的消息了,不过,他给我带来了一点体会:当一个行业井喷时,会有这样超出常理的天才涌现。

Bang 和他的 JSPatch

上面介绍的技术都是安卓平台的,iOS 和安卓平台的技术差异很大,像插件化这样的技术不太可能实现。在那几年里,iOS 讨论最多的是组件化。

不过,iOS 和安卓有一个共同的需求,那就是热更新,和安卓分发渠道太多不同,iOS 需要热更新,是因为苹果审核太慢,以及审核容易发生意外,虽然苹果有快速审核通道,但那远远不够。我们需要能绕过苹果审核的更新办法。Bang 的 JSPatch 应运而生。

Bang 曾经在百度工作过,后来去了微信读书,JSPatch 就是他在这段时间开发出来,并以个人名义开源的。

Bang 是潮汕人,2016 年我邀请他参加了第一届 GMTC 大会,在短时间的接触中,感觉似乎比较腼腆,但在网络上,他有一个博客,我很喜欢看他的博文,不仅言之有物,而且能切中要害。

Bang 因为 JSPatch 而名声鹊起,GMTC 的时候他的演讲爆满,有人专门过去看他。

JSPatch 并不是第一个 iOS 热更新工具,在之前还有基于 Lua 的 WaxPatch,后来由淘宝的君展维护,但 WaxPatch 需要带一个 Lua 运行时会增大体积,而 JSPatch 则颇为小巧,借助 iOS 平台内嵌的 JS 引擎,代码行数长期保持在 2000 行以下。从 2016 年起,我了解到的国内大多数头部应用,几乎全部使用了 JSPatch,包括互相之间存在竞争的 BAT 巨头们,在注重门户之见的国内,这实在是个了不起的成就。

然而,正因为 JSPatch 的流行,当苹果决定收紧审核政策时,JSPatch 首当其冲,结果让整个中文互联网几乎都受到了影响,这个下面再谈。

百花齐放的时代

2016 年,国内的移动开发技术发展到了最鼎盛的时期。插件化 / 热更新成为显学,成为高级工程师的必修课。

张勇在乐视最风光的时候去了乐视体育,后来又被人鼓动,以技术入股的形式去做 PC 安卓模拟器的创业。

360 安全卫士的张炅轩等,开发了一个更完美的插件化技术 RePlugin,并在 2017 年的 GMTC 上开源。

微信发布了 Tinker,美团发布了 Robust。

聚划算的朴诚发布了 LuaView,另一个基于 Lua 的 iOS 热更新工具。

刚刚收到苹果投资的滴滴宣布合并 Uber 中国,它招募了当时 iOS 领域的大牛 Sunny 孙源和安卓的任玉刚,开始在移动技术上大展拳脚。过不久,Sunny 就推出 iOS 动态化方案 DynamicCocoa,它比 JSPatch 更加激进,已经有安卓插件化的几分模样;曾鼓捣出 Dynamic-load-apk 的任玉刚则推出安卓插件化方案 VirtualAPK,与 RePlugin 同台竞技。

QQ 还推出了一个号称史上最疯狂的 iOS 动态化方案 OCS,它们开发了一个自己的中间语言 OCScript,还开发了一个自己的虚拟机 OCSVM 去执行它……稍微懂点编程的就知道这是一个多么疯狂的方案。

那的确是一个百花齐放的时代。而身处这个时代甚至参与其中,几乎每天我都活在激动当中。

很多人不知道的是,InfoQ 的使命是推动软件技术发展,这是一个颇显狂妄的说法,技术推动社会发展,而我们要推动技术发展。我将它当作了我的职业信条。在那段时间里,我能感觉到所处领域每天都在往前发展,能感觉到我所作的事情,无论是报道和微信社群,还是线下大会和沙龙,就像拓荒一样,都在一点点的推动这个领域的外延更加扩大。没有比这更好的工作了。

当时我发现一个问题,就是这些黑科技只在国内发展,没有人把它介绍到国外去,国内外之间缺乏交流。于是我给 InfoQ 英文站的社区编辑 Sergio De Simone 写了一封邮件,看看有没有可能对国内的技术做一些报道。Sergio 是一名软件工程师,业余时间帮 InfoQ 英文站写了许多技术报道,其中大部分是移动领域的。


然而 Sergio 的回复让我比较沮丧,他认为这些技术违反苹果和谷歌的规则,不太可能在国外应用,因此报道的兴趣不大。曾经动过想把张勇推荐到国外 QCon 的心思也熄灭了。

2016 年 6 月的闭门会议上,我号召大家多多在国外网站和社区上推广插件化技术,可惜没人听进去,在我了解的范围内,唯一做过这方面的努力的是 LBE 的冯森林,他在参加 Google IO 的时候向国外工程师演示插件化的神奇,据说当时老外惊呆了。后来谷歌推出了自己的免安装应用 Instant Apps,不知道是否有受到启发。

现在回想起来,我当时可以做得更多的,即使未必有用,但总得试一试。

然而还没有等我再次鼓起勇气,苹果的打击到来了。

苹果的一封信

2017 年 3 月,众多 iOS 开发者收到警告邮件,声称其 App 违规使用动态方法,责令限时整改。

这封邮件引起了开发者的恐慌,连 React Native 都遭受池鱼之殃,经过一番寻找之后,发现问题集中出现在两个热更新工具 Rollout 和 JSPatch 上,其中 Rollout 国外用的较多,JSPatch 则主要是国内使用。

在当时的分析文章里,该事件的影响一节里我写道:

在国外,本次警告事情其实受影响并没有那么大,国外 iOS 平台热修复或热更新并不流行,Rollout 的声明里,本次只有数百个 App、数百万最终用户受到影响。但在国内,这一数字要远远超出。去年以来,凡是公开分享过 iOS 应用架构的,都将热修复作为其基础设施之一,可以说大部分头部应用都有使用 JSPatch 或类似方案。本次受影响的国内 App 数以千计,覆盖的人群则包括几乎所有中国 iOS 用户。更长远的影响是,热修复对一个团队的开发流程和节奏紧密相关,很多团队都必须修改相应的开发流程来适应变化。

这一判断并没有夸张,在苹果警告之后,iOS 动态化的工具都转入地下发展,关于这方面的研究和分享也急剧减少,甚至连整个 iOS 技术的分享也变少了。在另一篇文章里,我写道:苹果的一封邮件像《三体》的智子一样锁死了国内技术。从那以后,“iOS 开发没人要了”成为一个梗,流行起来。



在安卓平台,虽然谷歌没有能力像苹果一样干涉国内的开发,但插件化技术从另一方面遭遇了困境。

这一困境就是安卓新版本以及国内各种魔改 ROM 对于底层的改动。安卓插件化技术依赖部分底层方法以及私有 API,而这些在新版本里是很有可能改动的,一旦修改了,插件化就会失效甚至出错。国内各大手机厂商的系统也喜欢对底层进行修改,它们的修改甚至都不会公开告知,因此兼容问题是插件化技术遇到的最大挑战。

2018 年发布的 Android 9.0,甚至要求开发者不得使用私有 API,少了这些 API,安卓开发被重新关回笼子里,还能玩的黑科技大大减少,无意之中竟然取得了和苹果警告类似的效果。

反 思

在苹果警告之后,我疯狂的阅读网上的报道,希望能看到转机,然而越是读下来,我的心里越是冰凉。

在 Hacker News 对于事件报道的讨论串里,大部分人对于苹果的行为持赞同态度,原因是隐私和安全。

插件化和热更新对隐私和安全的威胁在于,用户无法控制或得知应用被偷偷的嵌入恶意代码,部分插件化方案要求提前获取所有插件 App 所需要的权限,这意味着开发者可以利用它来窃取用户的隐私。

而隐私和安全在国外是一个禁区,不可越雷池一步,即使并没有造成实际危害,只是有这方面的风险,相关的技术就不可能被允许。这个,其实 Sergio 早在一年前就告诉我了,然而我还抱着侥幸心理,并没有重视他的回复。

插件化和热更新的问题就在于它们的能力太强大了,犹如过于锋利的双刃剑,从苹果和谷歌的角度,必须要加以限制。

事实上,插件化已经被拿来做过坏事了,DroidPlugin 就曾被黑产利用,在 2017 年爆发过 Triada 和 TigerEyeing 两起病毒木马事件。

至于热更新实际上尚未造成危害,它只是被一家国外安全机构检测到有风险,就遭到了苹果的坚决取缔。但在国内,它已有被滥用的苗头,在苹果警告事件中,有些没有使用热更新的 App 也收到了警告,后来才发现,有些第三方的 SDK 使用了 JSPatch,而这些第三方开发商做些什么,甚至连 App 开发者也不能控制!

所以,从这个角度来看,插件化和热更新是需要防止滥用,而在之前,我只看到这些技术好的一面,对于它们的负面影响刻意忽视,违背了媒体中立的准则和监督的职能,现在回想起来,实在是不成熟。

从另一方面思考,我又难掩愤懑之情——苹果和谷歌打击插件化和热修复,实在是太轻易了,并且,从始至终,它们从未与国内开发者有过沟通,从未考虑过国内的特殊情况。

苹果取缔热修复,只需要通过一封邮件,它甚至都不需要修改审核规则,而只需要暗中调整规则的解释:iOS 安全大牛蒸米在微博上说,警告中提到的动态方案,其实审核上一直写着不让用,但是实际用了审核也并不会被拒。而现在,它们只需要严格执行规则就行了。

而国内和国外,不仅在对待隐私上的态度不同,App 的形态上也有差异,国内的超级 App 带来的新需求,为什么不能让苹果为国内市场单独推出一些新功能和政策?

我深深的体会到,国内的这些移动开发技术,其实就是沙滩上的城堡,对于操作系统的路径依赖,让苹果和谷歌哪怕做一些小小调整,也足以让这些酷炫的黑科技遭到毁灭性打击。

这段经历让我无比渴望国内出现自主的操作系统,让我深刻理解了自主操作系统的重要性、底层技术和制定规则的重要性。然而我也知道,要做一款主流的操作系统,不仅仅是技术问题, 更重要的是历史的机遇。

更实际些的考虑,要尽量避免类似的事件发生,我们需要尽量加强国内外的技术交流,避免双方的技术差异过大。

2017 年 6 月的第二届 GMTC 的开场上,我提醒参会者:苹果和谷歌一直在坚定的推动 Web 技术,在热更新和插件化的道路选择上,我们和国外走得越来越远,这真的是一件好事情吗?我们是否走了弯路?

然而,这已经是马后炮,这些“黑科技”技术的衰落,已不可避免。

为了忘却的纪念

对我来说,这篇文章充斥着大量的回忆,格外难以动笔。

对于过去的事情,记忆难免有所美化,有些地方也可能记错,读者如果发现,还请海涵。

这段经历对我的打击甚大,在一段时间内,我甚至对移动技术失去了兴趣,沙滩城堡的意象在脑海中挥之不去,看到新的技术总有一个声音在耳边说:没有用的,只要苹果爸爸稍微改改,这技术就得进历史的垃圾堆……

在做移动开发内容和活动时交了一些朋友,也得罪了一些人,后来大部分也不再联系了……

插件化和热更新技术是真的不可避免的衰落了,它们已经错过了历史机遇期,新的技术已经从另一个维度实施了降维打击,没错,说的就是小程序。

据了解,有些大型 App 如淘宝等,已经开始用小程序来取代一些原生的功能模块,这一职责正是插件化的范畴,而小程序的热更新相比原生,更加简单和自然。小程序还能成为平台吸引第三方入驻,这在插件化中只有在 RePlugin 那里有些想法的雏形而已。

更大的改变则来自于行业风向的变化。头条系“App 工厂”取得的巨大成功,让人们重新思考 App 矩阵的价值,人们不再往超级 App 里加功能,而是又开始开发新的 App 了,对这些 App,插件化基本没有用武之地。

很多移动开发者都转行了,张勇最终还是和老板协商解决了问题,投身到下一份工作里。

Bang 去了蚂蚁金服,他还在坚持。在他 2018 年总结的博文里,他写道:

JSPatch 8 月开始遭受另一波审查升级,混淆的方案失效,苹果确实针对 JSPatch 做了比较厉害的扫描手段并在不断升级,今年跟审核团队沟通他们也是表示不喜欢 JSPatch,还是那套审核后不能修改的说辞,就算解决了安全问题也没用,比较无奈,但热修复需求还在,JSPatch 平台还是会继续找解决方案。

苹果仍然在赶尽杀绝。

插件化热潮注定成为技术发展的一段小插曲,也许再过几年,不会有人记得了。那段激情飞扬的岁月,终将成为 The Wasted Times。


(电影《罗曼蒂克消亡史 /The Wasted Times》剧照)

他一直拖到一九四九年五月初才坐上去香港的轮船,算得上真正的末班车。没有人知道他在拖什么或等待什么,我想他自己也未必知道,不过是下意识的拖延。不久他就死在香港,死前再没有值得记述的事件或说过的话,他基本没再说话,这没什么可奇怪的,一切都不值一提,他终于走向自己的沉默。

我很喜欢这段话,觉得感同身受,做过什么,发生什么,到末尾一切都不值一提,然而我终究还是拿起笔,记下那些为了忘却的纪念。