宁愿写两遍代码,也不用C++跨iOS、Android平台开发?

Dropbox 最近宣布将放弃用 C++ 编写跨 iOS、Android 平台代码,转而使用各平台的原生框架(Swift/Kotlin),理由是代码共享相关的隐藏成本太高。有趣的是,2014 年,Dropbox 在给 Facebook 的开发人员做分享时,却曾直言写两套代码会带来种种弊端,推荐使用 C++ 进行跨平台开发。为什么几年前的经验之谈却成了几年后的技术债?C++ 因何躺枪?跨平台开发前景如何?本文试图一一为你讲解。

事件综述

编写一次,随处运行。这句话不知道被多少工具和框架写在开篇介绍中,用以吸引更多公司和开发者。然而,作为这一策略忠实的实践者,Dropbox 近日宣布放弃这一想法,宁愿为不同的平台写两遍代码,也不用 C++ 跨 iOS、Android 两大平台共享代码的方式进行开发。

早在 2013 年,Dropbox 就采用上述策略进行移动开发,这背后的想法很简单:用 C++ 编写一次代码,而不是用 Java 和 Objective-C 编写两次。那时,整个移动工程团队相对还比较小,但需要支持快速增长的移动路线图。因此,公司希望找到一种方法,使这个小团队可以快速交付大量 Android 和 iOS 代码。

如今,Dropbox 完全放弃了这个策略,转而使用各个平台的原生语言(主要是 Swift 和 Kotlin,这两种语言在刚开始制定移动策略时还不存在)。这个决定的主要原因是代码共享相关的隐藏成本太高,这源于同一个基本问题:

以非标准的方式编写代码,使 Dropbox 承担了一些开销,而如果采用广泛使用的平台默认选项,就不必担心这些开销,这种开销最终比只编写两次代码要昂贵得多。

C++ 的问题?

随后,Dropbox 总结了成本太过高昂的主要原因,比如自定义框架和库的开销、自定义开发环境的开销、处理不同平台差异的开销以及培训、招聘和留住开发人员的开销,这与 C++ 本身存在的问题密切相关。但是,C/C++ 是唯一一种编译器同时受到谷歌和苹果支持的语言,如果使用其他语言将会产生一系列更麻烦的问题。

1、自定义框架和库的开销

关于使用 C++,最容易出现的开销就是构建框架和库,这大致可以分为两类:

  • 框架,让开发者与主机环境交互,构建一个功能齐全的移动应用程序。例如 Djinni 、用于在后台和主线程中运行任务的框架等。

  • 一些取代本可以在平台原生语言中使用的默认语言或开源标准的库。例如:用于 JSON(反)序列化的 json11 ;C++ 非可空指针 nn 。

如果使用平台原生语言,那么这些代码都是不必要的。当然,并不排除个别开发者可以很好地利用开源 C++ 库,但是 C++ 开发社区中的开源文化并不像移动开发社区那么强大(特别是几乎不存在的 C++ 移动社区)。这些成本在 C++ 中特别高(与 Python 或 C# 等其他可能的非原生语言相比),因为它缺少一个功能齐全的标准库。

虽然 C++ 标准委员会为了这门语言做了大量工作,甚至为了 C++20 的新特性举办了一场历史上规模最大的会议(180 人参会),试图通过会议确定哪些特性可以加入新版本,比如 Concepts、Ranges、Modules、Coroutines 等,但大部分开发人员并不认可此次调整,并将部分新特性归结为“语法糖”,在社交平台进行吐槽。

国外的一位游戏开发人员接连在社交平台发表看法,甚至发表了一篇长文声明自己不看好 C++20 的新特性,并认为新版本没有解决最关键的问题,这让其未出世就被群嘲。可见,整个 C++ 社区是存在一定问题的,很难让其达到移动开发社区中的活跃度,很多问题也是悬而未决。

2、自定义开发环境的开销

移动生态系统有许多工具可用来提高开发效率,但 C++ 社区就不太行。开发者必须投入时间构建支持 C++ 代码共享的工具。甚至,Dropbox 亲自做了定制构建系统,创建了包含 C++ 代码以及 Java 和 Objective-C 封装器的库,并且生成 Xcodebuild 和 Gradle 都能识别的目标文件。这个系统是一个很大的成本支出和拖累,因为需要不断更新以支持两个构建系统中的更改。

3、处理不同平台差异的开销

尽管 iOS 和 Android 应用程序都是“移动应用程序”,通常具有相同的特性和功能,但平台本身存在一些影响实现的差异,甚至不能真正地编写一次代码就在不同的平台上运行,必须花费大量时间将代码集成到不同的平台中,并编写特定于平台的代码(有时这些代码就位于 C++ 层)。

4、培训、招聘和留住开发人员的开销

如今,市面上越来越难招聘到具有相关 C++ 经验、对移动开发感兴趣的高级工程师。当然,如果愿意提高招聘成本,给出更有吸引力的薪水,这个问题不难解决。但是,怎么把这些开发人员留住也是一个大问题。事实上,移动开发社区有大量新技术和新模式层出不穷,移动开发者可能根本不想从事 C++ 项目开发。

如上种种,通过 C++ 共享移动代码的方式让开发成本变得非常高昂,这也让曾经的支持者 Dropbox 不得不选择放弃。

跨平台开发的 Yes or No

“Write once, run anywhere” 一直以来就是开发者的梦想。但从 Dropbox 的经验来看,实现之路堪称曲折甚至反复。

我们发现了一个特别有意思的新闻:2014 年,Dropbox 的开发人员给 Facebook 的开发人员做了个讲座,分享了他们使用 C++ 做跨平台开发的经验之谈。他们特别提到,iOS 和 Android 平台代码库的不一致会带来一系列问题:

  • 开发和维护成本成倍增加。

  • 开发团队需要多次修复同样的缺陷。

  • 针对某个平台报告的缺陷可能会被另一个平台忽视掉。

  • 不同平台的 App 行为可能会有预料之外的细微差异。

  • 性能优化成本高昂并且与平台相关。

Dropbox 赖以克服上述这些问题的策略基础就是为所有 UI 无关的代码建立一个共享的跨平台 C++ 库,例如数据和网络部分的逻辑。UI 部分还是使用原生代码编写,以便尽可能地利用平台对动画特效和设备传感器等的支持,并且确保充分的响应速度。

5 年过去,当年的经验之谈似乎变成了 Dropbox 的技术债,他们最终放弃了用 C++ 做跨平台开发,转而用各平台的原生语言做开发工作。

跨平台开发,是时兴潮流还是明日黄花?

2018 年,Airbnb 放弃使用 React Native,回归使用原生技术,究其原因主要是:项目庞大后,维护困难;第三方库良莠不齐;兼容上需要耗费更多精力……

腾讯客户端工程师方秋枋接受 InfoQ 采访时曾表示:

这些都是跨平台框架的通病。跨平台意味着需要花费很多时间来解决平台差异性问题,同时要面临第三方库不够原生平台丰富健壮的现状。跨平台其实是牺牲部分功能和体验,换取开发速度和一致性的权衡,并不是业务开发的银弹。

微信团队在跨平台开发方面的解决方案是:

目前微信团队并没有统一使用一套跨平台开发框架。我们也在积极探索各种可能性。

微信小程序,是跨平台开发的第一、二阶段的融合做法,利用 Webview 作为渲染容器,通过规定 Web 技术栈的子集(WXML、WXSS),让客户端获得更多的渲染性能优化空间,同时通过规范化组件的使用,逐步引入原生控件代替 Webview 渲染。

除此之外,我们还探索了基于 C++ 进行跨平台开发。实质上还是第二阶段的做法,只不过是使用 C++ 作为中间语言,去编写业务代码和调用原生控件。为什么要采用 C++ 呢?因为 C++ 安全性会更高一点,同时性能也会更佳。可以应用在一些对安全性要求比较高的场景。

Flutter 会是跨平台开发的终极之选吗?这个问题很有意义。能实现跨平台开发的框架也五花八门,让人眼花缭乱。最流行的跨平台框架有 Xamarin、PhoneGap、Ionic、Titanium、Monaca、Sencha、jQuery Mobile、React Native、Flutter 等等。但这些工具的表现也是高低有别,各有千秋。热度不减的只有 React Native 和 Flutter,原因在于这二者背后有 Facebook 和 Google 背书,开发者更加信任。

Flutter 的优势在于:它完全免费,彻底开源;可以用来更快地创建应用;具有出色的用户界面(UI);节省代码量;可接入平台原生功能;最适合 MVP 开发(最小化可行产品);较老的设备也使用相同 UI 运行应用;减少测试工作量;更丰富的社区支持;较低的维护难度;内置来自 Dart 的包管理器。

其中不少特性如节省代码量、接入平台原生功能、支持较老设备、较低维护难度等等,都击中了跨平台开发的痛点。业界认为,目前跨平台开发没有一个完美的解决方案,相对来说 Flutter 在兼容上做得好一点,通过自底向上自研框架来尽可能减少平台差异,但是可靠性仍需进一步检验,引入 Flutter 需要大家对自己公司业务有非常清晰的认知,并权衡好利弊。

随着 Google 对 Flutter 的持续加码,也许在不久的未来,Flutter 将成为跨平台开发的最佳解决方案。

适配 Android P之非SDK接口限制的排查方法

一次 Android 权限删除经历

资本寒冬下的 android 面经

点个在看少个 bug :point_down: