阿里妹导读:Android 转 Java 开发在技术栈上有哪些差异?思考和解决问题时又会有怎样的转变?本文分享阿里技术专家从 Android 开发转 Java 应用开发的心得感受,分析两者差异及在动态性、兼容性、内存管理和状态问题等方面的一些看法,并总结了在阿里做一个 Android 开发和 Java 开发所需要的技术栈。
写在前面
记得刚毕业那会儿,还是 BBA 争霸的年代,无线迎来一个黄金年代,如同当下的 “AI” 和更早些年的 “云”,什么事都需要往热点上靠一靠,基于 PC 的互联网公司们无不发出 all in 无线的战略口号,无线业务遍地开花,甚至一个公司每个产品线都恨不得出一个 App,直到现在再纷纷都向旗舰收拢,不过那都是后话了。
2013 年,就是在那样的环境下,发生了几件事情:iPhone 5s 发布、Android 升级到 4.3 版本,以及......我毕业了,哈哈。作为校招新人,对长远职业规划、技术栈发展潜力并没有太多的认识,只是跟大多程序员一样,都是希望能跟随甚至推进业界的潮流,而不是以熟悉 Android 某个版本某个 API 得先设置个 false 否则会花屏这样的二手知识为荣,但是很不幸,由于种种选择和被选择,最后锚定了百度上海的校招研发岗位,一下子集齐了我最讨厌的两样东西:Java 和客户端!
就这样也踉踉跄跄在 Android 客户端上耕耘了近 6 年之久,经历了 2.3 与 4.0 的兼容地狱,webkit 到 chromium 的升级、dvm 到 art 的转变以及热修复、各类动态部署狂潮,Android studio 也从 0.9 Beta 到如今的 4.0,并完全消灭了上古的 Eclipse + ADT,也算是有点感情了。直到去年,终于有机会拥抱了变化,得益于团队内有多个业务方向,避免了转岗入坑,顺利转战了 Java 应用战场,如今也快到 1 年了,突然也有些感想,与大家分享。
除了 Java 本身,其实都不一样
从 Android 转客户端会更容易么
陆续地,团队也有其他同学投入到了 Java 应用的怀抱,向着 “全栈” 开发发展,其中还不乏之前做 IOS 客户端开发的同学。闲暇之余,也会交流一些心得,比如 IOS 的同学会觉得 Android 转 Java 开发占了比较大的优势,从 IOS 转就会显得额外的困难,毕竟 OC 或 Swift 和 Java 大概长得完全不一样,但从一个前 Android Dev 的视角来看,我在这方面的感受是:
从我个人学 OC 的经验看,我觉得 OC 同学学 Java 还是要比 Java 同学学 OC 简单多了,Java 更接近自然语言的表达,并且,好歹大家大学都还是学过一点的吧~
当没有了 Java 语言本身的优势,Android 与 Java 应用几乎没有半毛钱关系,能直接拿来就用的框架微乎其微,硬要说还有优势的话,只能说 Android studio 也是基于 Intellij Idea 的,开发起来还算顺手。
当然,不能说的太绝对,不然容易招杠精,毕竟 Java 本身是为那些框架能力提供铺垫,客户端也用了 DVM(类 JVM)的 JNI 能力来做热修复,用了动态代理,甚至也有用 cglib、aspectJ 实现 AOP 的方案。但在一个生产环境中,一个语言本身提供的能力是完全不够的,一个语言是否能被广泛使用和接受,还有个主要因素就是得看生态,看社区,看大家贡献的库,这也是 Java 为什么虽然可能不是最好的语言,但仍然是使用最广泛的语言的原因,从这个角度看,它们之间,除了语言,真的没有太多相同的地方。
技术栈差异
以在阿里的生产环境为例,要做一个 Android 开发或者 Java 开发 “攻城狮”,你大概需要的知识图谱如下(图是根据自己的认知整理的,有不详尽甚至错误的地方望指出):
Android 开发:
Android 技术栈知识图谱
Java 应用开发:
Java 技术栈知识图谱
可以看到,从大类看其实都是通的,无非是基础的框架、扩展的库或中间件、以及一些列的发布、监控等支撑平台,套路上无论做什么技术估计都是这样吧,但偏向性却有本质的区别。面向客户端的 Android Framework 核心解决的问题是事件交互、生命周期、视图绘制问题、处理人机交互的逻辑,而 Java 服务端常用的 Spring 框架核心更关心服务之间的耦合、依赖、面向大规模集群扩展的能力。基础框架不同,必然类库、中间件也会有本质的区别,几乎就没有共性了,这些由设计思路带来的不同势必也要求开发的同学需要在转换开发角色时转换思考方向。
思考问题的角度转变
翻看以前的代码,记得刚从 C/C++ 学 Java 的时候,还在学生的时代,总会喜欢一个 main 函数带着一群 static 方法来实现主流程,又到后来学 Python 用于一些数据分析和脚本处理,也总是焦虑没有地方声明变量类型,对于 a = {} 这样的 map 声明方式也很不习惯,所以,不光是 do as Romas,Think as Romas 才是转变中最重要的东西,不仅仅只是换了一些库和工具。
动态性
动态性曾经是客户端最为看中的能力,从热修复到动态 dex loader,到 RN、Weex、Dinamic 一系列动态能力的建设,可以说是面试必考题。无论是 Android 菜鸟还是 Android 专家,都要让你聊一聊你知道的动态技术方案以及他们之间的区别,几个大厂之间的热修复方案对比更是每个 Android Dev 都需要准备的分享 PPT,Android 如此,IOS 也不例外,直到 IOS 禁止了动态加载的能力。
对于客户端来讲,发布周期与服务端有着较大的区别,按照之前我们在手淘的集成经验,Android 端覆盖 80% 的用户大概需要 3-5 天的时间。早些年更慢,因为还没有 App 商店的统一静默升级,还需要在各个应用市场更新或者推送更新,简直五花八门,用户也不胜其烦。IOS 会稍微好一些,古早就有了应用市场,但一个更新周期毕竟还是远远大于后端的周期,应用发布都是以分钟最多是小时级别记。这样的区别导致客户端的开发过程必须要考虑:
使用动态框架,从应急修复到动态发布,一方面是应急,另一方面是加快版本迭代和收敛效率。那么,设计的业务框架、写的组件是不是有动态修改、扩展的可能性,是否存在编译、混淆优化导致无法热修复的风险,是设计和开发过程中要考虑的点。
无法挽回的发布。不仅仅是因为面向用户,只要发出去的版本有问题,大概率是没有时间和机会挽回的,容易出较大的舆情和故障,所以客户端的测试工作也会比较辛苦,全功能的回归是发布前必走的流程。
但这些问题在 Java 应用上很不显著,虽然也有热部署的框架,但仅仅也是为了部署效率,并不会作为核心的能力提供。
兼容性
同样是因为迭代周期长,并且无法做到真正全量更新,客户端还会面临一个较大的问题就是版本兼容。目前 Android 已经更新到 11 了,但市场上也不排除有 4.x 的版本,同样,App 发展到 X 版本了,市场上同样遍布这 1-N 的各个版本。版本覆盖后的数据兼容,特别是 sqlite 升级,会让 Android 的开发者们苦恼万分,也经常容易出现问题而导致启动闪退,被迫让用户重新安装。同样的问题在服务端还是比较少出现的,唯一可能的就是协议的变更,但往往也可以在短时间内进行共存和迁移。
除了端上数据的兼容性,另一个兼容性就是 API 及设备的兼容性,特别在 2.x 到 4.x 的年代。同样的视图实现在不同的 Android 机器上、在不同的 API Level 上都可能出现不同的表现,修改方案也往往顾此失彼,往往一次修改完就得所有主流机型回归一遍,对于在 Android 中重 UI 的开发同学,简直有摔手机的冲动。当然现在 API 日渐完善,compat 包也解决了很多兼容问题,开发体验已有质的飞跃。从这方面说,Java 应用面临的问题又要简单不少,部署环境有问题?用 Docker 虚拟化!简直乐无边!
内存管理
但服务端应用并不意味着更简单。老实说,去年差点没被线上 FullGC 弄死。一大波流量进来,持续的 FullGC 导致集群假死,大量的服务超时引来客户投诉,重启扩容都无法解决,还得靠找到问题的根源后才能解决。FullGC 是 Java 应用常见的问题,但在客户端是完全不需要关注的。客户端分配给 App 的内存通常只会有 128M~256M,与服务端动辄几个 G 的内存管理不能同日而语,并不过分关心 GC,除非是在极致的性能优化场景,更别说是用 G1 还是 CMS 了(当然 Android 也没有)。通常只关心泄露和 OOM,毕竟 OOM 才是客户端唯一的内存杀手,所以才有了大波的图片加载库,来解决对象的内存管理问题,毕竟一个 App 在你的手机里存活不太可能以天计,在前台的时间更是少之又少,不太必要考虑长时间运行下的累积问题。
状态问题
客户端的状态一般都是保存在页面本身的。页面、视图作为一个对象,获取数据,渲染,展现给用户,并负责与用户交互,反馈修改页面状态等一系列动作,几乎把一个页面内的所有行为状态都封闭了起来,形成交互对象内的强耦合。但 Java Beans 可能并不是这么想,所有的 Beans 原则上都是无状态的,数据由专门的 db 或中心存储,这也是分布式系统的基本要求,这点上是设计思维的最大转变。
客户端有并发问题么,其实也有。Android 支持 AsyncTask,支持HandlerThread,甚至更底层的 Thread,Looper,但客户端的线程数通常是个位数或者几十个。异步线程的模型也相对简单,一般都只是处理一些数据,毕竟 UI 刷新还得在主线程完成(SurfaceView,TextureView 除外),主要的逻辑都在主线程的交互事件处理,几乎不存比较难解的同步问题。
但 Java 应用在这里面要考虑的东西就太多了,多线程同步、一致性、原子性、锁,展开讲可能一本书都讲不完,或许这也是 Java 应用迷人的地方。
写在最后
说了这么多,不善言辞,也是随性发挥,想到啥说些啥,更多是对自己开发历程的一些感受。要细说差别,还有太多没有提及的方向,知识图谱中的每个点拿出来都还可以细品。但想表达的无非还是在开发的过程中,每一行代码,每一种设计模式的使用,思考的重点和角度都会有所不同。我们常常听到 “全栈” 两个字,但要我说,会多少语言,熟悉多少框架并不是全栈的含义,能尝试不同的技术栈,从不同的视角来思考并解决问题,融会贯通,才是全栈赋予我们最大的价值,这也是我个人追求的技术之 “道”。