如何通过 Firebase Remote Config 实现非代码层面的功能开关?

解读

国内面试中,这道题考察的是“灰度/热修/运营配置”落地能力,而非单纯调用 SDK。面试官想确认:

  1. 你是否真正理解 Remote Config 的“服务端控制→客户端无感更新”本质;
  2. 能否在合规(工信部 164 号文、个人信息保护)前提下,把开关与业务解耦,做到“不发版、不强制更新、可回滚”;
  3. 是否具备“兜底、缓存、一致性、可观测”的工程化思维,而不是 Demo 级调用。

知识点

  1. Firebase 中国加速节点与代理配置:国内需接入上海/香港 CDN,并在 AndroidManifest 中声明 firebase_remote_config_auto_fetch 为 false,避免首次拉取被 GFW 重置。
  2. 服务端参数结构设计:采用“三段式” key = module_subModule_switch,value 用受控 JSON(如 {"on":true,"whiteList":[...],"percent":10}),既支持布尔,也支持白名单+百分比灰度。
  3. 客户端双缓存策略:应用启动时优先使用 FirebaseRemoteConfig#activate() 上一次缓存,再异步 fetch() 新值;若 500 ms 内拉取失败,则回滚到本地 defaults.xml,保证首帧可用。
  4. 合规与隐私:Remote Config 不携带个人敏感字段,但白名单灰度需用 MD5 后的设备标识;在《隐私政策》中显式列出“我们使用 Firebase 进行功能配置”,并通过 MIIT 备案。
  5. 一致性保障:对同一用户同一版本,使用 Settings.Secure.ANDROID_ID 做一致性哈希,确保灰度结果不跳变;同时记录 lastFetchTimefirebaseInstallationsId,方便后台审计。
  6. 可观测与回滚:在 APM(如腾讯 Bugly 或阿里 SLS)埋点 rc_key=xxx,rc_value=xxx,rc_fetch_latency=xxx,一旦崩溃率上涨,可在 Firebase Console 一键回滚,5 分钟内全网生效。
  7. 与 CI/CD 集成:在 GitLab-CI 中通过 firebase-tools 命令行 firebase remoteconfig:rollback --version-number $RC_VERSION 实现自动化回滚;同时把参数表存到企业微信文档,运营可直接编辑,无需研发介入。

答案

  1. 工程配置
    build.gradle 中引入国内 BOM:
    implementation platform('com.google.firebase:firebase-bom:32.7.0')
    implementation 'com.google.firebase:firebase-config'
    并在 Application#onCreate() 初始化:
    FirebaseApp.initializeApp(this)
    val rc = Firebase.remoteConfig
    rc.setConfigSettingsAsync(remoteConfigSettings {
    minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 0 else 3600
    fetchTimeoutInSeconds = 5
    })
    rc.setDefaultsAsync(R.xml.remote_config_defaults)

  2. 参数设计
    以“短视频水印开关”为例,Console 新建参数 shortVideo_watermark_switch,默认值 false,灰度规则:

    • 白名单:内部员工 200 台设备 IMEI(MD5)
    • 百分比:10% 用户随机桶
    • 版本号:≥ 7.30.0
      服务端 JSON 值:{"on":true,"whiteList":["a35f2c...","e4bc1d..."],"percent":10}
  3. 客户端解析
    在短视频拍摄模块注入 Repository:
    class FeatureSwitchRepository @Inject constructor(
    private val rc: FirebaseRemoteConfig
    ) {
    fun isWatermarkEnabled(): Boolean {
    val raw = rc.getString("shortVideo_watermark_switch")
    val bean = Gson().fromJson(raw, WatermarkBean::class.java)
    if (bean.whiteList.contains(md5(DeviceUtil.getIMEI()))) return true
    val bucket = (DeviceUtil.getAndroidId().hashCode() and Int.MAX_VALUE) % 100
    return bucket < bean.percent && bean.on
    }
    }
    ViewModel 中通过 StateFlow<Boolean> 暴露,UI 层 Compose 直接 collectAsState(),实现真正的非代码切换。

  4. 兜底与回滚

    • 首次安装无网络:使用 defaults.xml 中 false,保证水印关闭,符合工信部“最小可用”原则。
    • 线上事故:Console 把 shortVideo_watermark_switch 值改为 {"on":false},5 分钟内所有客户端下次拉取生效;若需秒级回滚,调用 rollback API 并触发 Push 透传消息,客户端收到后立刻 rc.activate().await()
  5. 合规埋点
    isWatermarkEnabled() 返回值处埋点:
    APM.onEvent("rc_switch", mapOf(
    "key" to "shortVideo_watermark_switch",
    "value" to result,
    "fetch_latency" to (SystemClock.elapsedRealtime() - fetchStart)
    ))
    数据上报走国内 SLS 通道,不含 IMEI 明文,满足《个人信息保护法》。

拓展思考

  1. 多进程场景:Remote Config 默认只在主进程拉取,若短视频模块运行在 :video 独立进程,需在 ContentProvider 初始化时把主进程缓存序列化到 MMKV,子进程读取,避免重复 fetch。
  2. 与 Tinker 热修协同:若开关触发代码补丁下载,需保证补丁版本号也写入 Remote Config,防止“旧客户端 + 新补丁”导致 ABI 不一致。
  3. 离线优先策略:国内地铁、电梯场景网络不稳定,可结合 WorkManager 每 6 小时触发一次 fetch(),并设置 isDeveloperMode=false,避免频繁请求被 Firebase 限流。
  4. A/B 测试升级:Remote Config 可与 Firebase A/B 或国内腾讯兔小巢对接,把开关升级为实验层,自动计算置信区间;但需注意实验参数不能超过 500 个,否则 Console 加载卡顿。
  5. 合规沙盒:Android 14 引入 READ_MEDIA_IMAGES 新权限,若开关涉及权限弹窗,需在 Console 增加 targetSdkVersion 判断,避免低版本用户被误开权限请求,导致应用市场审核被拒。