作者 | 曹立成
10 月 21 日我在上海举办的 QCon 全球软件开发大会上分享了 Kotlin Multiplatform 相关的内容,这里记录成文章,分享给你。
1 为什么选择 Kotlin 跨平台?
我先介绍一下我们团队的情况,交代清楚我们技术选型的背景。
我们是阿里巴巴 1688 无线团队,主要负责阿里巴巴客户端、1688 工业品、商家版和采源宝四个 App,是 Android & iOS 混合团队,双端的同学都有。主要负责基础业务、搜索、首页、用增相关等内容,会经常遇到多个 App 部分业务需要复用的情况。在日常开发中,会遇到以下几个问题:
- 基础模块逻辑相对复杂,多端研发成本高
- 双端逻辑代码膨胀,无法保证完全一致,问题排查难
- 逻辑代码穿插在各个模块中,现有跨端复用方案迁移成本高
这些问题可能很多存量 App 都会遇到,随着不断迭代,很多逻辑写着写着双端就出现不一样的细节了,出了问题不好查,迭代维护成本又高,如果再出现人员变动,那一部分代码就会变成「祖传代码」。
为了解决这些问题,我们希望寻找一种方法,可以将逻辑收拢,提升研发效能,降低维护成本。从技术栈的角度出发,我们对比了 Flutter、Kotlin Multiplatform、React Native 三种方案:
我们的诉求是,能够收拢逻辑层面的东西,UI 并不重要,因为我们已经有统一协议渲染框架。而且我们需要更好的性能,对原生能力的融合(不要桥),以及较低的上手学习成本和更轻量化的方案。所以我们选择了 Kotlin Multiplatform。在下文中,我会多次提到 KMM 这个简称,这里先简单讲一下 KMM 是什么:
- 基于 KMP(Kotlin Multiplatform)派生,全称 Kotlin Multiplatform Mobile。它不是一个框架,是一种研发范式,通过代码管理与插件,降低 Kotlin 跨 Mobile 端上手门槛
- 更贴合 Android & iOS 平台。Android 平台产物 aar,iOS 平台使用 Kotlin Native 编译,产物 framework
- 与平台无关的 common 逻辑可以跨更多的平台,如 Mac、Linux、Windows、Web 等
2 Kotlin 是如何实现跨平台的?(技术解析)
代码工程结构
对于 Kotlin Multiplatform 来说,代码工程结构会和其他的工程不太一样。官方给出的基础结构是一个 commonMain 的代码包,其他平台的代码包依赖它(jvmMain、jsMain 等)。对于 KMM 工程来说,通过 Android Studio KMM 插件生成的结构是这样的:
如何扩展原生能力
先举一个最简单的例子,实现一个双端的 Log 工具。在 commonMain 里定义好 expect fun,然后分别在 iosMain 和 androidMain 中实现 actual fun。
1 1 = 2 的例子举完了,在真正的应用开发中,调用 Android 能力是非常简单的,和普通 Android 开发没有区别,只要通过 gradle 引入需要的库,就可以调用里面的代码了。iOS 则是通过 cinterop 这个能力生成 Kotlin 识别的三方库头文件实现能力调用的。
cinterop 是 Kotlin Native 支持的能力,KMM 工程的编译使用了 gradle 工具链,其中对 iOS 来说,我们使用了 Kotlin CocoaPods 插件。引入一个三方库,首先通过 pod 脚本写好库和版本,接着执行 gradle sync,其中会执行两个关键的 task,第一个是 podGen,它会把我们需要依赖的库通过 pod 拉到本地,生成一个壳工程,然后会执行对应库的 cinterop task,执行成功以后会生成上图里的 klib 文件,里面包含了导出的头文件函数(knm 文件),最后代码里 import 进来就可以直接使用了。
内存管理机制
- Android:现代垃圾回收算法
- iOS(Kotlin Native):基于引用计数的垃圾回收算法,额外增加了对环的处理
多线程实践
- 不可变状态才允许多线程访问(Kotlin Native 中不可变不只是 val ,必须是 frozen 状态)
- 使用协程库
- 使用 @ThreadLocal 或 @SharedImmutable
- 封装平台特性的线程方法(线程池、Handler、GCD)
- 使用原子类(Atomic)
3 Kotlin Multiplatform 在阿里巴巴的实践
1688
日志
我们在日志建设上使用了 Kotlin 收拢双端逻辑。对于偏中间件的开发来说很好理解,Kotlin 调用双端基础能力,提供对上的 API 即可。代码示例如下:
上层入口提供了 log 的 API,调用的就是 Kotlin 代码中 LogKit 里的 log 方法。对于 log 相关的配置和策略,通过 LogConfig.kt 封装实现。最终上报也是使用了原生的上报通道,对接了原生已有的能力,比如 TLog ,这是我们的个案日志上报系统,expect 定义好 fun,双端各自实现。整个日志模块中 Kotlin 部分,起到一个承上启下的作用。
搜索筛选
搜索筛选是个业务场景,1688 APP 首页就可以直接跳转到搜索,是一个非常大的流量入口。搜索逻辑比较复杂,有不同场景的搜索、不同品类的搜索、下拉的筛选项、侧边栏的筛选项等。在开发维护搜索逻辑的时候,我们遇到过好几次由于双端逻辑不一致,出现问题很难排查的情况。为了追求双端逻辑强一致,我们把搜索筛选业务的请求策略、场景管理、筛选模型、埋点策略封装在了 Kotlin 中。
FilterManager 是最核心的入口,用户点击筛选项就会走到这里。通过筛选模型和场景模型联合处理,最终拿到请求参数给 UI 层,再通过我们已有的统一协议渲染框架进行渲染。从收益上看,我们的人力投入减少了大概 30%,稳定性持平,逻辑强一致,维护成本降低,排查问题成本也降低。
饿了么商家端
饿了么商家端也深度使用了 Kotlin 收拢双端逻辑,还使用了 Redux 管理数据流。这里就不展开细讲了,详细内容可以查看 PPT,会在文末给出链接。
4 应用场景
总结一下,Kotlin Multiplatform 比较适合以下场景:
- 没有 UI 的复杂逻辑场景
- 基于 MVP 设计的复杂业务中 M 和 P 层
不太适合以下场景:
- 涉及 UI 的场景
- 动态化较多的场景
5 研发模式
根据我们的实践经验,我给大家推荐三种研发模式:
对于基础库开发来说,其实比较简单,Kotlin 把双端逻辑收拢,提供对上 API 调用双端能力即可。对于常规业务开发来说,我更推荐 MVP 设计法则,把 M 和 P 层收拢在 Kotlin 中。如果有多端业务开发的场景(常见于 B 端业务),逻辑层可以用 Kotlin 跨更多的平台,饿了么商家端和美团商家端就有在这个场景下使用。
6 最后
目前在用 Kotlin Multiplatform 进行开发的国内互联网公司有:阿里巴巴、美团、携程、快手,腾讯也在跟进中。
追求双端逻辑一致性,是目前很多存量 App 的诉求。使用 Kotlin Multiplatform 实现逻辑跨端,既可以低成本地满足逻辑一致性诉求,又可以提升研发效能。
对于客户端技术团队,我推荐大家试一试,还挺香的。如果遇到什么问题,也可以联系我一起交流,我的联系方式也在 PPT 里。
附上 PPT 下载链接:https://ppt.infoq.cn/slide/show?cid=92&pid=3441