1. 跨平台项目初始化与目录结构设计

跨平台项目初始化与目录结构设计 学习卡片

为什么初始化阶段就要设计目录结构

跨平台项目最容易失控的地方不是业务代码,而是平台差异代码的散落。如果一开始就把 Android、iOS、Web、Windows、macOS 的适配代码混在业务目录里,后续每加一个平台都会牵动全局。因此初始化阶段的核心任务只有两件:建立一套共享代码库,以及用目录边界把平台差异隔离在固定位置。

本例采用 Flutter 作为跨平台骨架(同一套 Dart 代码可编译到移动端与桌面端),目录设计遵循一条原则:共享层只依赖抽象接口,平台层只负责实现接口。这样新增平台时只需新增一个平台目录,而不修改共享层。

目录结构设计

下面是一套可直接落地的目录骨架,核心是把代码分为 core(与平台无关的共享层)、platform(平台差异实现)、features(业务功能)三层:

cross_platform_app/
├── lib/
│   ├── main.dart                 # 入口,按平台选择适配实现
│   ├── core/                     # 共享层:不 import 任何平台库
│   │   ├── models/               # 数据模型
│   │   ├── services/             # 业务服务(依赖抽象接口)
│   │   └── contracts/            # 平台能力抽象接口
│   │       └── platform_info.dart
│   ├── platform/                 # 平台差异层:只实现 contracts
│   │   ├── platform_info_stub.dart      # 默认/兜底实现
│   │   ├── platform_info_mobile.dart    # Android / iOS
│   │   ├── platform_info_desktop.dart   # Windows / macOS / Linux
│   │   └── platform_info_web.dart       # Web
│   └── features/                 # 业务功能,只依赖 core
│       └── home/
│           └── home_page.dart
├── test/
│   └── core/                     # 共享层可脱离平台单测
├── pubspec.yaml
└── analysis_options.yaml

关键实现:用条件导入隔离平台差异

Dart 的条件导入(conditional import)让同一份共享代码在不同平台编译时自动选择对应实现,这是目录隔离能真正生效的机制。

先在 core/contracts/platform_info.dart 定义抽象接口:

// lib/core/contracts/platform_info.dart
abstract class PlatformInfo {
  String get platformName;
  bool get isDesktop;
}

// 条件导入:默认用 stub,移动端/桌面端/Web 各自替换
import 'package:cross_platform_app/platform/platform_info_stub.dart'
    if (dart.library.io) 'package:cross_platform_app/platform/platform_info_io.dart'
    if (dart.library.js_interop) 'package:cross_platform_app/platform/platform_info_web.dart'
    as impl;

PlatformInfo createPlatformInfo() => impl.createPlatformInfo();

其中 platform_info_io.dart 再根据 Platform.isAndroid / Platform.isWindows 等运行时判断,返回移动端或桌面端实现。共享层只调用 createPlatformInfo(),永远不直接 import 平台库。

初始化命令与验证

  1. 创建工程:flutter create --platforms=android,ios,windows,macos,web cross_platform_app
  2. 按上面的结构建立 core/、platform/、features/ 三个目录。
  3. 在 main.dart 中调用 createPlatformInfo(),把 platformName 显示在首页。
  4. 验证:分别运行 flutter run -d windows 与 flutter run -d chrome,首页应显示不同平台名,而 core/ 与 features/ 代码完全未改动。
判断目录设计是否合格的标准:删除任意一个 platform/ 下的文件,只有对应平台编译失败,其余平台不受影响。
共享层 / 平台层 / 业务层 依赖关系与条件导入路径 core/ 共享层(平台无关) contracts/platform_info.dart(抽象接口) services/ 业务服务(依赖接口) models/ 数据模型 features/ 业务功能层 home/home_page.dart 只依赖 core,不感知平台 platform/ 平台差异层(实现接口) platform_info_stub.dart 默认兜底实现 platform_info_io.dart Android / iOS / Windows / macOS / Linux platform_info_web.dart Web(dart.library.js_interop) 条件导入选择规则 dart.library.io → io 实现 dart.library.js_interop → web 实现 否则 → stub 兜底 依赖 条件导入 createPlatformInfo() 实现接口 新增平台 = 新增一个 platform 实现文件,core 与 features 保持不变 删除任一 platform 文件,仅对应平台编译失败,其余平台不受影响

可扩展性检查清单

检查项合格标准不合格信号
共享层纯净度core/ 中无 dart:io、dart:html 等平台库core 里出现 Platform.isAndroid
接口收敛平台能力都定义在 contracts/业务代码直接 import platform 目录
新增平台成本只新增一个实现文件需要改动 core 或 features
可测试性test/core/ 可脱离设备运行单测必须启动模拟器
不要把平台判断写成散落的 if (Platform.isAndroid)。一旦这类判断超过三处,目录隔离就形同虚设,应立刻收敛回 contracts/ 接口。