设计动态配置系统
问题
如何设计一个 Android 动态配置系统(Remote Config),实现不发版即可动态调整 App 行为?
答案
整体架构
SDK 设计
class RemoteConfig private constructor(context: Context) {
private val memoryCache = ConcurrentHashMap<String, ConfigValue>()
private val diskCache = ConfigDiskCache(context)
private val defaults = mutableMapOf<String, Any>()
// 三级回退:内存 → 磁盘 → 默认值
fun getString(key: String, default: String = ""): String {
return memoryCache[key]?.asString()
?: diskCache.get(key)?.asString()
?: (defaults[key] as? String)
?: default
}
fun getBoolean(key: String, default: Boolean = false): Boolean {
return memoryCache[key]?.asBoolean()
?: diskCache.get(key)?.asBoolean()
?: (defaults[key] as? Boolean)
?: default
}
// 拉取最新配置
suspend fun fetch() {
val response = api.fetchConfigs(
appVersion = BuildConfig.VERSION_NAME,
deviceId = DeviceId.get(),
)
// 更新内存和磁盘缓存
response.configs.forEach { (key, value) ->
memoryCache[key] = value
}
diskCache.putAll(response.configs)
}
// 设置默认值(App 启动时调用)
fun setDefaults(map: Map<String, Any>) {
defaults.putAll(map)
}
companion object {
@Volatile private var instance: RemoteConfig? = null
fun getInstance(context: Context): RemoteConfig {
return instance ?: synchronized(this) {
instance ?: RemoteConfig(context.applicationContext).also {
instance = it
}
}
}
}
}
更新策略
| 策略 | 实现 | 时效性 |
|---|---|---|
| 定时轮询 | WorkManager 每 4h 拉一次 | 小时级 |
| 冷启动拉取 | Application.onCreate 后台 fetch | 启动时生效 |
| 推送触发 | FCM/厂商推送 → 触发 fetch | 分钟级 |
| 长连接推送 | WebSocket 直推配置变更 | 秒级 |
配置生效时机
关键问题:配置拉取到后立即生效还是下次启动生效?
- 立即生效:适合开关类配置(Feature Flag)
- 下次生效:适合 UI 布局等需要整体刷新的配置
推荐用 fetchAndActivate() + fetch() 两种 API 分别对应两种场景。
灰度与条件下发
// 服务端配置条件下发规则
data class ConfigRule(
val key: String,
val value: Any,
val conditions: List<Condition> // 满足所有条件才下发
)
data class Condition(
val type: ConditionType, // APP_VERSION / OS_VERSION / DEVICE_ID / PERCENT
val operator: Operator, // EQ / GTE / LTE / IN / REGEX
val value: String
)
// 例:新功能只对 versionCode >= 100 且灰度 10% 用户开放
// conditions: [
// { type: APP_VERSION, op: GTE, value: "100" },
// { type: PERCENT, op: LTE, value: "10" }
// ]
常见面试问题
Q1: 配置拉取失败怎么办?
答案:
采用三级回退策略确保 App 永远不会因为配置问题崩溃:
- 内存缓存 — 上次成功拉取的值
- 磁盘缓存 — 上次持久化的值(跨进程重启仍有效)
- 代码默认值 —
setDefaults()设置的兜底值
网络失败时静默使用缓存,不影响用户体验,后台重试即可。
Q2: 如何实现配置的 A/B 测试?
答案:
对同一个配置 key 设置多个变体(variants),用户按设备 ID hash 分桶:
配置 key: "new_checkout_flow"
变体 A (50%): true → 新流程
变体 B (50%): false → 旧流程
客户端 SDK 上报设备信息,服务端根据分桶策略返回对应变体值。结合埋点数据就能对比两组用户的转化率。