多环境配置(dev/test/prod)与打包策略
这是 Flutter 实战系列第 3 篇。
目标:把“环境隔离 + 打包发布”做成一套可长期维护、可团队协作的工程方案。
1. 问题背景:业务场景 + 现象
中大型项目里,通常至少有三套环境:
dev:联调、快速验证test:测试回归、提测验收prod:正式线上
如果环境治理不到位,会出现这些高频事故:
- 测试包误连线上接口
- 正式包带了调试开关或测试埋点
- iOS/Android 包名(BundleId/ApplicationId)和渠道配置混乱
- 发布前靠“手改常量”,极易出错
- CI 无法稳定复用同一构建流程
2. 原因分析:核心原理 + 排查过程
2.1 核心原理
多环境的本质不是“多套 if-else”,而是 “编译时注入 + 运行时消费”:
- 编译时决定:入口、包名、签名、图标、构建参数
- 运行时只读配置:API 域名、日志级别、开关策略
2.2 常见反模式(踩坑总结)
- 用
kDebugMode区分 dev/test/prod(这是错误的,debug/release不是业务环境) - 把域名写死在业务代码中
- 通过手动改文件来切环境
- Android 有 flavor,iOS 却没有 scheme 对齐
- 本地能打包,CI 打不出来(流程不可脚本化)
3. 解决方案:方案对比 + 最终选择
3.1 方案对比
方案 A:单入口 + 大量环境判断
优点:上手快
缺点:后期逻辑污染严重,易误发
方案 B:多入口 + 原生 flavor/scheme + 配置中心
优点:清晰、可自动化、风险低
缺点:初期配置略多
3.2 最终选择
采用 方案 B:
- Dart 多入口(
main_dev.dart/main_test.dart/main_prod.dart) - Android 用 Product Flavor
- iOS 用 Scheme + Build Configuration
- 环境参数统一走
--dart-define(或--dart-define-from-file) - 发布脚本统一命令,不靠手工点点点
4. 关键代码:最小必要代码片段
4.1 环境枚举与配置模型
enum AppEnv { dev, test, prod }
class EnvConfig {
final AppEnv env;
final String apiBaseUrl;
final bool enableLog;
final String appNameSuffix;
const EnvConfig({
required this.env,
required this.apiBaseUrl,
required this.enableLog,
required this.appNameSuffix,
});
}
4.2 统一环境初始化入口
class Env {
static late EnvConfig current;
static void init(EnvConfig config) {
current = config;
}
}
4.3 三个入口文件(示例)
main_dev.dart
void main() {
Env.init(
const EnvConfig(
env: AppEnv.dev,
apiBaseUrl: 'https://api-dev.example.com',
enableLog: true,
appNameSuffix: ' DEV',
),
);
bootstrap();
}
main_test.dart
void main() {
Env.init(
const EnvConfig(
env: AppEnv.test,
apiBaseUrl: 'https://api-test.example.com',
enableLog: true,
appNameSuffix: ' TEST',
),
);
bootstrap();
}
main_prod.dart
void main() {
Env.init(
const EnvConfig(
env: AppEnv.prod,
apiBaseUrl: 'https://api.example.com',
enableLog: false,
appNameSuffix: '',
),
);
bootstrap();
}
4.4 网络层读取环境配置(禁止写死)
class ApiClient {
static String get baseUrl => Env.current.apiBaseUrl;
}
4.5 --dart-define 方式(推荐)
如果不想维护三套入口,也可以单入口 + define:
const envName = String.fromEnvironment('APP_ENV', defaultValue: 'dev');
启动命令:
flutter run --dart-define=APP_ENV=dev
flutter run --dart-define=APP_ENV=test
flutter run --dart-define=APP_ENV=prod
4.6 Android Flavor(示例)
android/app/build.gradle 关键结构:
android {
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev"
resValue "string", "app_name", "YourApp Dev"
}
test {
dimension "env"
applicationIdSuffix ".test"
resValue "string", "app_name", "YourApp Test"
}
prod {
dimension "env"
resValue "string", "app_name", "YourApp"
}
}
}
构建示例:
flutter build apk --flavor dev -t lib/main_dev.dart
flutter build apk --flavor test -t lib/main_test.dart
flutter build apk --flavor prod -t lib/main_prod.dart
4.7 iOS Scheme(建议)
- 创建
Dev / Test / Prod三个 Scheme - 对应三套 Build Configuration(如
Debug-Dev,Release-Test,Release-Prod) - Bundle Identifier 与显示名后缀区分(避免同机覆盖)
构建示例:
flutter build ipa --flavor prod -t lib/main_prod.dart
注:iOS 的 flavor 依赖你在 Xcode 侧 scheme/configuration 的映射是否配置正确。
5. 效果验证:数据 / 截图 / 日志
- 接口域名验证:启动日志打印当前
apiBaseUrl - 环境标识验证:App 首页显示 env 角标(dev/test)
- 包名验证:安装 dev/test/prod 可并存
- 日志开关验证:prod 构建关闭 debug 日志
- 签名验证:Android keystore、iOS provisioning 与环境匹配
- CI 验证:同一脚本可稳定产出三环境包
建议保留一行启动日志(发布时极有用):
debugPrint('ENV=${Env.current.env}, BASE_URL=${Env.current.apiBaseUrl}');
6. 可复用结论:通用经验 + 避坑清单
6.1 通用经验
- 环境差异放“配置”,不要放“业务逻辑”
- Debug/Release 与 Dev/Test/Prod 是两套维度,必须分开治理
- Android Flavor 与 iOS Scheme 必须一一对应
- 打包命令必须脚本化,避免人工操作
- 线上包默认最小化信息暴露(关闭调试开关与冗余日志)
6.2 避坑清单
- 不要把线上 key、域名硬编码进页面/VM
- 不要在发版前手改常量切环境
- 不要只配 Android 不配 iOS
- 不要忽略“同机并存”能力(dev/test/prod 易互相覆盖)
- 不要让 CI 与本地命令不一致
附:可直接复用的命令模板
# Dev 调试
flutter run --flavor dev -t lib/main_dev.dart
# Test 调试
flutter run --flavor test -t lib/main_test.dart
# Prod 预发验证(release)
flutter run --release --flavor prod -t lib/main_prod.dart
# Android 打包
flutter build apk --release --flavor prod -t lib/main_prod.dart
flutter build appbundle --release --flavor prod -t lib/main_prod.dart
# iOS 打包
flutter build ipa --release --flavor prod -t lib/main_prod.dart
这套方案的核心价值是:环境切换可重复、发布流程可自动化、线上风险可控。只要把入口、原生 flavor/scheme、配置中心三件事统一起来,后续多人协作和 CI/CD 都会顺畅很多。