第15章 跨平台架构设计:Android Auto与CarPlay的共性抽象、业务逻辑复用、UI层分离
做车载开发这些年,我最大的体会就是:别把平台当上帝。Android Auto 和 CarPlay 虽然底层机制完全不同,但业务逻辑其实高度相似。你想想看,导航、音乐、电话、消息——这些核心功能在两个平台上几乎一模一样。
我见过太多团队,先做 Android Auto 版本,再从头搞 CarPlay。结果代码重复率高达 60% 以上。维护两个独立代码库,改个 bug 要改两遍,加个功能要加两遍。说白了,这就是在浪费生命。
15.1 共性抽象:找到两个平台的“最大公约数”
跨平台架构的核心,就是抽象。把 Android Auto 和 CarPlay 的共性提取出来,形成一个中间层。这个中间层不依赖任何平台 API,只处理业务逻辑。
核心原则:业务逻辑层永远不要直接引用 Android 或 iOS 的命名空间。一旦引用了,你就被绑死了。
我习惯把共性抽象分为三个层次:
- 数据模型层:定义统一的媒体项、导航指令、联系人等数据结构
- 服务接口层:定义播放控制、导航引导、消息通知等行为接口
- 状态管理层:管理播放状态、导航状态、连接状态等
举个例子,媒体播放的共性抽象:
// 跨平台媒体项定义
public class MediaItem {
public String id;
public String title;
public String artist;
public String albumArtUrl;
public long duration;
public boolean isPlayable;
}
// 跨平台播放控制接口
public interface IMediaPlayer {
void play();
void pause();
void skipToNext();
void skipToPrevious();
void seekTo(long position);
MediaState getCurrentState();
}
这个 MediaItem 和 IMediaPlayer 在 Android Auto 和 CarPlay 两端都能用。我在项目中就是这么干的,效果出奇的好。
15.2 业务逻辑复用:一次编写,两处运行
业务逻辑复用,说白了就是把“怎么做”和“在哪做”分开。业务层只管“怎么做”,平台层只管“在哪做”。
我建议采用策略模式 + 依赖注入的方式来实现复用。核心业务逻辑写成抽象策略,平台差异通过注入的方式传入。
我的经验:把业务逻辑拆成一个个独立的 Service,每个 Service 只做一件事。比如 NavigationService 只处理导航逻辑,MediaService 只处理播放逻辑。这样测试起来也方便。
来看一个导航业务复用的例子:
// 跨平台导航服务
public class NavigationService {
private INavigationPlatform platform;
public NavigationService(INavigationPlatform platform) {
this.platform = platform;
}
public void startNavigation(Route route) {
// 业务逻辑:计算路线、检查交通状况
Route optimizedRoute = optimizeRoute(route);
// 平台调用:交给具体平台去显示
platform.displayRoute(optimizedRoute);
platform.startGuidance(optimizedRoute);
}
private Route optimizeRoute(Route route) {
// 这里全是纯业务逻辑,不依赖任何平台
// 可以写单元测试
return route;
}
}
// 平台接口
public interface INavigationPlatform {
void displayRoute(Route route);
void startGuidance(Route route);
void updateTrafficInfo(TrafficInfo info);
}
你看,NavigationService 里全是纯业务逻辑。Android Auto 和 CarPlay 各自实现 INavigationPlatform 接口就行。我曾经在一个项目里,用这种方式把 80% 的导航代码变成了共享代码。
15.3 UI层分离:各平台各玩各的
UI 层就别想着复用了。Android Auto 用模板化 UI,CarPlay 用 UIKit 或 SwiftUI,两者差异太大。强行统一 UI 层,只会让你陷入无尽的适配地狱。
我的原则是:UI 层完全分离,只通过 ViewModel 或 Presenter 与业务层通信。
注意:千万不要在 UI 层写业务逻辑。哪怕是一个简单的“如果播放状态为暂停,显示播放按钮”,也要放到 ViewModel 里处理。UI 层只负责渲染和事件传递。
来看一个推荐的架构分层:
┌─────────────────────────────────────────┐
│ UI 层(平台专属) │
│ Android Auto: 模板化 Layout / Fragment │
│ CarPlay: CPInterfaceController │
└──────────────────┬──────────────────────┘
│ 数据绑定 / 事件回调
┌──────────────────▼──────────────────────┐
│ ViewModel / Presenter 层 │
│ (平台无关,但可感知平台) │
└──────────────────┬──────────────────────┘
│ 接口调用
┌──────────────────▼──────────────────────┐
│ 业务逻辑层(完全复用) │
│ MediaService / NavigationService │
└──────────────────┬──────────────────────┘
│ 数据访问
┌──────────────────▼──────────────────────┐
│ 数据层(部分复用) │
│ 网络请求 / 本地存储 / 数据库 │
└─────────────────────────────────────────┘
嗯,这里要注意:ViewModel 层虽然平台无关,但需要感知平台的生命周期。比如 Android Auto 的 onCreate、onDestroy,CarPlay 的 templateWillAppear、templateDidDisappear。我习惯在 ViewModel 里定义统一的生命周期回调接口,由平台层去调用。
15.4 架构全景图
下面这张图是我自己项目里用的架构,你可以参考一下:
15.5 避坑指南
我曾经踩过不少坑,这里分享几个最痛的:
- 别在业务层用平台线程:Android 的
Handler、iOS 的DispatchQueue都不要出现在业务层。统一用Executor或Task抽象。 - 数据模型别太“胖”:我见过有人把 UI 相关的字段塞进数据模型,结果换平台时改得想哭。数据模型只放业务数据,UI 展示逻辑交给 ViewModel。
- 接口粒度要适中:太细了调用链长,太粗了耦合度高。我一般按功能模块划分,一个模块一个接口。
- 别忘了错误处理:两个平台的错误类型不同,业务层要定义统一的错误码。我曾经因为忽略这点,导致 CarPlay 端崩溃了三天才找到原因。
我的建议:先画好接口图再写代码。把每个 Service 的输入输出、依赖关系理清楚。磨刀不误砍柴工,这个步骤省不得。
15.6 总结
跨平台架构设计,说白了就是三个字:分、抽、合。分的是 UI 层,抽的是共性接口,合的是业务逻辑。做到这三点,你的代码就能在两个平台间自由穿梭。
我个人习惯在项目初期就搭好这个架子。虽然前期多花点时间,但后期维护起来真的省心。你想想看,改一个 bug 只需要改一处,加一个功能只需要加一个 Service——这种爽感,只有经历过的人才知道。
公众号:蓝海资料掘金营,微信deep3321