跨平台客户端开发经验总结

1 前言

因为《猫科动物和企鹅在窗口外看机器人吃苹果》这出戏太美,很多公司都希望能全平台同时发布新版本app,还要能复用代码以降低开发成本。这迫使一部分已熟悉某个平台的先锋们转岗到另一个平台边学习边开发。最终有少部分人在每个战场都打拼过,从而能全盘考虑以设计出良好的可复用架构。
革命总会出新知,今天已有一些开源框架能应对同时在5大平台Mac OS X、Linux、Windows、Android、iOS上开发客户端,新人们可以不再过多关心系统底层的那些事儿。不过多学点还是有很大好处的,最简单地概括就是会发现大道归一,多种编程语言、系统API、开发工具其实都是解决同一堆问题。越新的方法越方便,可是却算不上越简单。在理解这些之后,就会对编程乃至程序员职业本身有了更深层的看法。
(想到什么写什么,其实远远没总结完)

2 开发

2.1 开发环境

程序员肯定喜欢集代码编辑、编译、打包等一体化的IDE。然而好的IDE都不跨平台,比起写代码,掌握所有平台的官方tool chain是一件比较无聊的事,可是又重要得必须去做。

作用/工具/平台 Windows Max OS X、iOS Android Linux 跨平台
IDE Visual Studio Xcode Eclipse / IntelliJ IDEA 无官方版 自己打造=跨平台代码编辑软件+快捷键+命令行工具
工程文件 VS、Makefile xcodeproj Java IDE的、Android.mk Makefile cmake、gyp
代码编辑 可用IDE 可用IDE 可用IDE 无主流 Vim、Emacs、Sublime Text、Eclipse
命令行编译 cl、devenv、nmake xcode-build(以前是gcc,现在是clang) ant、ndk-build、gradle gcc、make gcc、clang
调试 VS、WinDbg Xcode(GDB、LLDB) JDB、GDB GDB
打包 Windows Installer、第三方工具 Xcode+zip aapt dpkg、checkinstall

很多好用的工具是某平台特有的,可以帮助检查多平台共有的部分代码,如Linux下的valgrind帮助检查内存泄露,Xcode自带Analyze静态检测代码等。
无论是什么工具,查阅说明文档和用户手册是最基本的工作。因为信息量太大且应用场景过于局限,会难有做笔记的动力,用到什么就查什么,多查几次自然就记住了。
如果一段时间内都用同一种工具,记住功能、快捷键、面板设置等能大大加快工作效率。
如果是人力资源充足,最好能有专人负责所有平台的基础设施建设,而不是一个平台一个专家。在这方面研究透彻,后面的工作会事半功倍。

另外,跨平台开发肯定会用到虚拟机(Virtual Box或VMWare)。利用磁盘共享功能,可以修改代码后立刻在多个平台编译调试。推荐以Linux为Host,因为它工具多、系统性能高,能让工作效率最大化。

2.2 编程语言

理论上说,可以用C/C++写遍全平台(有转译神器的语言就不提了)。然而除非是自虐,否则没什么理由不在Android和iOS上分别用到一些Java和Objective-C,因为那样更容易调用便利的系统API。总之,做跨平台开发是不能只懂C/C++的。
而且,一般会用到脚本做一些自动化处理,例如检查代码规范、按文件模板生成代码、按配置打包资源文件等。目前流行的是python,因为它跨平台。跨平台的另外还有perl和ruby。如果算上Cygwin的帮助,那么shell就不仅仅是*nix专用的脚本语言了。不跨平台的话,Windows是batch file,Mac是AppleScript。
如果不幸碰上客户端需要显示网页(移动端上使用WebView),还少不了要懂点HTML/CSS/JavaScript。

语言学多了,会发现有许多共通点。

跨平台客户端开发经验总结_第1张图片

编程语言的最终目标都是为了生成CPU指令,只不过是承担了越多工作的编程语言,需要程序员写的代码越少而已。形象点说就是所有语言都是汇编的子孙,基因大部分是相同的,长得胖的穿衣服少罢了。所以,随着语言越学越多,每学一种新的都会越快上手。
学多了会混乱,编程书籍或网页参考常备身边是个好办法。

除了C++,各种语言都已和操作系统结合或者封装了系统的底层调用,以另外的模块/包的形式提供线程、进程等操作。越新的语言屏蔽了越多操作系统的细节,连C++11开始也提供了线程的操作。

另外,动态语言的特性在客户端开发的过程中被广泛利用。主要目的是解耦和调试。

  1. Objective-C动态语言的特性
  2. Java的反射
  3. 跨编程语言调用
    • C++调用命令行
    • JNI
    • python调用C/C++

2.3 操作系统

编程语言用到极致,最多也只能做纯数据运算。程序与外部交互的媒介是操作系统,所以跨平台开发还是得熟悉各个操作系统的特性。编程到了一定阶段,查系统SDK/API的用法会成为常态。最后你会发现,无非也就这几类:

  1. 窗口
  2. 进程与进程间通讯
  3. 原子操作与锁
  4. 线程与线程本地存储
  5. 消息循环
  6. 绘图/排版/动画/矢量图
  7. 文件读写
  8. 网络与协议
  9. 时间与日期
  10. 用户事件(鼠标、键盘、触碰)
  11. 设备事件(USB、陀螺仪、GPS、显示器/屏、电量等)
  12. 影音播放
  13. 动态库
  14. 权限、安全、验证
  15. 驱动与内核

使用第三方库能简化系统API和弥补不足,例如boost库能屏蔽很多系统API的差异,并有很多强大的工具。然而不能期望第三库能满足所有的需求,需要自己开发特定需要的库并不断积累,或许有一天还能贡献反哺给开源社区。
如果是做游戏客户端,以上需要关心的东西会更少,除非重新写一个游戏引擎。

为了符合平台用户体验、效能效率最优化、提升开发速度等目的,有时候我们必须使用特定操作系统的功能与界面,所以并不是对系统浅浅了解即可。根据开发的目标不同(特别是涉及硬件的),有时候还要挖掘底层API来用。

程序的一些额外处理:多语言、配置管理、初始化……

2.4 一些跨平台编程的坑

每个平台的坑都多如牛毛,是程序就有缺陷。举几个例子:
Windows带来的宏,如max、AddJob,这些不能作为函数名,否则Linux编译过了Windows会不过。


size_t在printf中的表示,Linux是%zu,Windows是%lu,没有通用的。当然,可以把这些warning关掉。


在linux平台能稳定运行的代码,在windows不一定行。例如

const char* GetString() {
  std::string temp = "1234";
  return temp.c_str();
}

因为STL的实现可能不同。还有系统内存回收机制也不同。


有些库只是特定平台需要,zlib(libz)就只有Windows需要。其余系统有自带的。


还有一些不算坑,仅算差异,但为了全平台能编译得过,仍需要记住。例如DEBUG宏不是所有平台都有的,动态库的接口导出写法不同:

#if defined(WIN32)
#  if defined(MY_IMPLEMENTATION)
#    define MY_EXPORT __declspec(dllexport)
#  else
#    define MY_EXPORT __declspec(dllimport)
#  endif
#else
#  if defined(MY_IMPLEMENTATION)
#    define MY_EXPORT __attribute__((visibility("default")))
#  else
#    define MY_EXPORT
#  endif
#endif

3 维护

3.1 架构设计

把架构设计放在维护这节是故意的,因为在跨平台产品中,可维护性是一切变态需求和bug的救星,这正正体现了架构设计的价值。
设计的结果基本上都是把程序分为三大层,从低到高是:平台无关层->平台抽象层->平台相关(适配)层。一些特定平台有的feature会作为component穿插到适配层中。

平台无关层包含数据处理和逻辑控制。数据处理是指通用数据描述(XML、JSON、Google ProtocolBuf),持久化存储,字符编码/多语言/国际化(ICU),图片解码,音频/视频解码,资源管理,log等。逻辑控制多是业务流程的实现。

平台抽象层把平台间有差异的部分做抽象,由平台相关层实现。这层是最考验设计功底的。

平台相关层多数跟UI有关,少数是此平台的特色业务需要,在移动端有衔接编程语言的作用。移动端会使用系统UI构件以求符合对应的用户体验,而且这些构件不是C++的。
当然,UI部分也能复用跨平台,不使用系统构件,完全自绘。这个方案的另一个好处就是容易换肤。不过系统构件一般都有硬件加速,自绘的性能相对较低。

更细致的划分就叫模块了。

3.2 测试与自动化

移动端是各平台的测试中最麻烦的,然而越是苛刻越是以那个平台为准。
如果由人工来保证每个平台都时刻正常那几乎是不可能的,所以要很多检测和测试需要依赖自动化。例如:

  1. 静态代码分析。如Xcode的Analyze。还可能做代码规范的检测,如cpplint。C++的代码自动格式化可以用clang-format.
  2. 编译与打包通过性测试。
  3. 单元测试、集成测试、系统测试、Monkey测试等,这些测试包含了兼容性测试。
  4. 内存泄漏检测。如Linux下的Valgrind、LLVM提供的ASan、Xcode的Leaks等。
  5. 性能测试。

为了实现这些测试,也是要写代码的。至少集成测试就没有通用的,要自己写。为了完成这些测试,又少不了用到各种第三方代码和工具。

自动化平台的工具推荐用buildbot。
从测试工具的举例可以看到,跨平台代码可以被多个特定平台才有的工具检测,对可靠性有非常大的帮助。

3.3 发布后

一般要做三件事:

  1. 崩溃日志收集以及log自动化分析。可以使用Google的breakpad。
  2. 统计信息与运营分析。
  3. 程序更新通知与信息推送。

这些是需要客户端配合的,但主要的工作量在后台。点到为止。
多平台共同开发有个好处:iOS的发布麻烦,及时发现bug慢,可以先在Android发布来弥补。

4 收获

坦白讲,最终的收获不怎么有趣,因为你看清了现实;然而又有些兴奋,因为下一步可能是创造新世界。
看清现实:

  1. 其实无论使用哪种语言或者在哪个操作系统编程都一样,虽然解决的问题不同,但手段是相似的,大道归一,程序的世界就是0和1。
  2. 做客户端应用程序开发,实际就是在使用各种API,极致境界是开发驱动程序和研究系统原理。
  3. 为了更好地开发,有很多“开发支持”领域中的事情不得不做,不少也是要写代码的。
  4. 程序最终是为产品服务的。软件的创新最终会是内容和功能的创新,“制作”本身终会到达瓶颈。

创造新世界:

  1. 最优架构设计或最优算法。(对,这就是程序员走向架构师的理由,不想继续Ctrl C+Ctrl V了)
  2. 各种新工具的开发,为提升效率而努力。
  3. 开发一门跨平台语言或者一个新系统?对技术有追求的,可以紧跟时代脉搏,学尽各种新语言,了解语言的趋势。最厉害的当然是发明一种新语言(及其配套的编译、调试工具)甚至一种操作系统了,不知道有生之年能不能看到中国人的发明呢。
  4. 知道了“怎样”编程,代码都写腻了,对编“什么样”的程序产生了兴趣,要转岗产品经理试试吗?

后续补充:《客户端开发设计总结》
http://blog.csdn.net/hursing/article/details/52586541

转载请注明出处:http://blog.csdn.net/hursing

嗯,干完跨平台项目就是这个样子:
跨平台客户端开发经验总结_第2张图片

你可能感兴趣的:(Android,iOS)