环境变量:什么是、使用和在移动项目中的配置

作者: IT Sectr 发布日期: 2026-05-31 阅读时间: 8 分钟

环境变量是动态值,在启动时传递给应用程序以配置其行为而无需修改代码。它们允许分离开发、测试和生产环境的配置。根据Twelve-Factor App, 2025,配置应存储在环境变量中,而非代码中。环境变量确保API密钥、后端URL和功能标志的安全管理。

主要内容

  • 环境变量将应用程序配置与源代码分离,适用于不同运行环境
  • .env文件以KEY=VALUE格式存储变量,并通过.gitignore从仓库中排除
  • iOS使用xcconfig和Build Settings在编译阶段传递变量
  • Android使用BuildConfig和gradle.properties生成配置字段
  • 安全:密钥和令牌应通过CI/CD加载,而非存储在代码或仓库中

什么是环境变量

环境变量是一个键值对,通过操作系统API可被应用程序进程访问。它们在进程创建时传递给进程,并且只在其运行期间存在。与嵌入在源代码中的配置参数不同,环境变量不需要重新编译就能改变值。这是Twelve-Factor App的基本原则,确保代码和配置之间的清晰分离。

在移动开发中,环境变量解决了不同环境的配置问题:开发人员使用本地服务器,测试人员使用staging,用户使用生产环境。不用将三个后端URL存储在代码中并使用if-else条件运算符,开发人员在构建阶段通过环境变量传递一个URL。这简化了代码,并消除了在测试环境中意外使用生产服务器的风险。

主要优势是安全:敏感数据不会进入代码仓库。API密钥、Firebase机密、后端访问令牌和证书通过CI/CD直接加载到构建环境。如果攻击者获得了代码仓库的访问权,他们在那里找不到机密,因为它们存储在CI系统的受保护存储中,只在构建二进制文件的阶段传递。

为什么移动开发中需要环境变量

移动项目至少有三个环境:development、staging和生产。每个环境都需要自己的配置集合:服务器URL、包名、签名方案和推送通知证书。如果没有环境变量,开发人员必须在每次构建前手动修改配置,这导致错误:测试构建中遗忘的生产密钥可能会导致向真实用户发送通知或消耗付费API。

环境分离

环境变量允许无需修改代码即可切换后端:只需替换变量API_BASE_URL中的值。功能标志(feature flags)通过类似FEATURE_CHAT_ENABLED=true的变量管理,这允许在staging中启用新功能而不影响生产环境。每个环境都创建自己的.env文件,在构建阶段加载。

dart
class AppConfig {
  static final String apiBaseUrl =
    const String.fromEnvironment('API_BASE_URL',
      defaultValue: 'http://localhost:8080');
}

密钥安全

硬编码的密钥是移动应用程序的常见漏洞。攻击者使用jadx或Hopper等工具反编译APK或IPA,并从二进制文件中提取机密。即使混淆也不保护字符串字面量——它们在反编译后很容易在代码中找到。环境变量解决了这个问题,通过CI/CD在构建阶段传递密钥,并在日志中遮盖。

kotlin
object Config {
    val apiKey: String =
        System.getenv("API_KEY") ?: throw
            IllegalStateException("API_KEY not set")
}

CI/CD集成

环境变量与构建管线集成:GitHub Actions、GitLab CI、Bitrise和CircleCI支持机密变量,这些变量不会显示在日志中。在构建阶段,CI根据分支或标签插入相应值:对于develop分支使用staging,对于v*标签使用生产环境。这自动化了流程,消除了人为因素,确保每次构建都获得正确的配置组。

.env文件和管理库

.env文件是以KEY=VALUE格式存储环境变量的标准方法。它不包含在仓库中,而是将.env.example作为所有变量的模板和空值添加到仓库。每个开发人员创建自己的.env文件,包含本地设置,不会影响团队其他成员的配置。不同环境使用不同的文件:.env.dev、.env.stage、.env.prod。

bash
# .env.example — 开发人员模板
API_BASE_URL=http://localhost:8080
FEATURE_CHAT_ENABLED=true
SENTRY_DSN=

对于移动项目,有专门的用于处理.env文件:

  • flutter_dotenv (Flutter) — 通过dotenv.load()在运行时加载.env中的变量
  • BuildConfig (Android) — 从build.gradle值生成类型化字段
  • xcconfig (iOS) — 将配置文件连接到不同的Xcode构建方案
  • react-native-config (React Native) — 通过.env文件管理变量

CI/CD中的分支设置允许替换不同的.env文件:.env.dev用于测试服务器,.env.stage用于预发布,.env.prod用于在应用商店发布。包含机密的文件从安全存储(Vault、AWS Secrets Manager)加载,不存储在仓库中。这确保即使版本控制系统被泄露,机密也保护有劳。

iOS项目中的环境变量

iOS生态系统使用xcconfig文件在构建层级上管理变量。它们连接到Xcode方案,并允许覆盖Debug和Release配置的值。xcconfig文件支持继承:可以创建一个包含共同设置的基础文件和针对每个环境的特定文件。

配置xcconfig文件

xcconfig文件以KEY = VALUE格式存储变量,并通过Configuration设置连接到Xcode中的构建方案。来自xcconfig的变量可通过$(VARIABLE_NAME)语法在Info.plist中访问,这允许为不同方案使用不同的包标识符和应用名称。为了快速识别环境,在应用名称后添加Dev或Staging后缀。

bash
# Config/Dev.xcconfig — 开发配置
API_BASE_URL = http://localhost:3000
BUNDLE_ID_SUFFIX = .dev
APP_DISPLAY_NAME = MyApp Dev

用于读取变量的Swift代码

在iOS中访问运行时变量时,使用Configuration.swift文件,它通过Bundle.main.object(forInfoDictionaryKey:)从Info.plist读取值。这种方法确保变量在构建阶段确定,并在启动后立即可用于应用程序。值在模块初始化时只读取一次,并缓存以便在应用程序生命周期内快速访问。

swift
enum AppEnvironment {
    static var apiBaseURL: URL {
        guard let urlString = Bundle.main
            .object(forInfoDictionaryKey: "API_BASE_URL"),
              let url = URL(string: urlString as! String)
        else { fatalError("API_BASE_URL is not configured") }
        return url
    }

    static var isChatEnabled: Bool {
        Bundle.main.object(
            forInfoDictionaryKey: "FEATURE_CHAT_ENABLED"
        ) as? Bool ?? false
    }
}

Android项目中的环境变量

Android通过BuildConfig支持环境变量——这是一个自动生成的类,其字段在模块的build.gradle文件中定义。BuildConfig在编译阶段为每个flavor和构建类型分别创建。这允许为调试和发布版本设置不同的值,而无需在代码中使用条件运算符,这提高了性能和安全性。

配置BuildConfig字段

BuildConfig字段通过在defaultConfig或特定buildTypes中的buildConfigField设置。每个环境创建单独的buildType或productFlavor。这确保了配置的严格隔离:调试版使用本地服务器,发布版使用生产环境。BuildConfig字段是静态类型的,这消除了在代码中引用时的错误。

groovy
// build.gradle (Module: app)
android {
    defaultConfig {
        buildConfigField "String", "API_BASE_URL",
            "\"http://localhost:8080\""
    }
    buildTypes {
        debug {
            buildConfigField "String", "API_BASE_URL",
                "\"http://dev.api.itsectr.com\""
        }
        release {
            buildConfigField "String", "API_BASE_URL",
                "\"https://api.itsectr.com\""
        }
    }
}

用于共同值的gradle.properties

项目根目录中的gradle.properties文件存储全局Gradle变量。它们可通过$variableName语法在所有模块中访问,用于指定依赖版本、构建标志和API密钥。与BuildConfig不同,gradle.properties仅在Gradle配置阶段工作,而非应用程序运行时。因此,在gradle.properties中指定的密码和API密钥在反编译的代码中不可见,因为它们仅用于在编译阶段生成BuildConfig。

groovy
# gradle.properties
SENTRY_DSN=https://key@sentry.io/project
MAPS_API_KEY=AIzaSy...

为了在Android项目中安全传输机密,建议使用local.properties(从VCS中排除)或通过System.getenv()从 CI/CD变量加载值到build.gradle。这确保密钥不会进入仓库。在Google Play Console上发布时,确保所有调试密钥已通过不同的buildTypes或productFlavors以相应的BuildConfig值替换为生产版本。

常见问题

可以在Flutter中使用环境变量吗?

可以,Flutter通过flutter_dotenv包支持运行时访问环境变量,或通过原生渠道访问平台变量。在Dart中,还可使用String.fromEnvironment构造器通过--dart-define在编译阶段传递值,这是Flutter项目的推荐方法。

BuildConfig和gradle.properties之间有什么区别?

BuildConfig是一个带有类型化字段的Java类,在编译阶段为每个buildType和flavor生成。gradle.properties是一个带有键值对的文本文件,在构建配置阶段可被所有Gradle模块访问。BuildConfig在应用程序运行时工作,而gradle.properties仅在Gradle脚本中工作。

如何防止.env文件泄露到仓库?

.env添加到仓库的.gitignore文件中。只向仓库提交.env.example,包含空值和每个变量的说明。对于CI/CD,使用GitHub Actions、GitLab CI或Bitrise设置中加密的机密,这些机密在日志中被遮盖并且在构建完成后无法读取。

如何通过CI/CD传递环境变量?

大多数CI系统支持机密环境变量。在GitHub Actions中称为Secrets,在GitLab CI中称为CI/CD Variables,在Bitrise中称为Secrets。在构建阶段,它们通过process.env或System.getenv()传递给构建脚本。机密变量不会显示在构建日志中,也不会在仓库的fork中可用。

什么是通过环境变量的功能标志?

功能标志是布尔变量,控制功能的开启或关闭而无需重新编译代码。例如:FEATURE_NEW_PAYMENT=true在staging中启用新支付系统进行测试。在生产环境中,同一标志设置为false,直到后端完全部署。这允许分阶段安全地实施变更,并在出现问题时回滚。

总结

  • 环境变量将配置与源代码分离,适用于不同开发环境
  • .env文件与.env.example模板是团队中区分环境的变量管理标准
  • iOS xcconfig将配置文件连接到Xcode方案,支持继承和Info.plist集成
  • Android BuildConfig从build.gradle为每个buildType单独生成类型化字段
  • CI/CD机密在构建阶段传递敏感数据,而不存储在仓库中
  • 功能标志通过变量允许在特定环境中启用功能而无需重新编译
  • 安全:密钥在CI中加密,不会进入应用程序的可反编译二进制文件

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读