4. 实现监听权限的底层 ContentObserver:注册与注销、回调转 Flow、安全问题
ContentObserver 是 Android 监听系统设置(包括权限)变化的底层回调机制。本章将拆解它的注册与注销生命周期、如何将回调转换为结构化 Flow,以及必须防范的线程与泄漏安全问题。
4.1 核心原理与类图
Android 权限变更通过 Settings.System、Settings.Secure、Settings.Global 以及 AppOpsManager 的 Uri 通知。ContentObserver 观察指定 Uri,当内容变化时触发 onChange。其本质是 Binder 驱动的进程间回调,运行在注册时指定的 Handler 线程上。
关键类
ContentObserver(Handler?)– 抽象基类,须重写onChangeContentResolver.registerContentObserver(Uri, boolean, ContentObserver)– 注册ContentResolver.unregisterContentObserver(ContentObserver)– 注销
易错点:第二个参数
notifyForDescendants 设为 true 会同时监听子 Uri 变化;对于精确权限 Uri(如 package:)应设为 false 避免重复触发。
4.2 注册与注销的完整实现
以下代码演示在 ViewModel 中使用 ContentObserver 监听 Settings.System.SCREEN_BRIGHTNESS 的变化(模拟权限相关 Uri 监听)。注意配对生命周期。
class PermissionObserver(handler: Handler) : ContentObserver(handler) {
private val _flow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val flow: SharedFlow<Unit> = _flow.asSharedFlow()
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
_flow.tryEmit(Unit)
}
}
class MonitorViewModel(application: Application) : AndroidViewModel(application) {
private val context = application.applicationContext
private var observer: PermissionObserver? = null
fun startObserving(permissionUri: Uri) {
if (observer != null) return
val handler = Handler(Looper.getMainLooper())
observer = PermissionObserver(handler).also { obs ->
context.contentResolver.registerContentObserver(
permissionUri,
false, // notifyForDescendants
obs
)
}
}
fun stopObserving() {
observer?.let {
context.contentResolver.unregisterContentObserver(it)
observer = null
}
}
override fun onCleared() {
stopObserving()
super.onCleared()
}
}
| 方法 | 触发时机 | 线程 | 注意事项 |
|---|---|---|---|
registerContentObserver | App 主动调用 | 任意线程 | 指定 Handler 为 null 则回调在 Binder 线程池 |
onChange | 系统设置变更 | 注册时指定的 Handler 线程 | 不要在 onChange 中做耗时操作 |
unregisterContentObserver | App 主动调用 | 任意线程 | 必须在注册时相同的 ContentResolver 上调用 |
4.3 回调转 Flow:安全且可组合
将 onChange 回调转为 SharedFlow 或 callbackFlow,可利用 Flow 的背压、取消、重试等能力。推荐使用 callbackFlow 在协程中自动管理注册与注销。
fun Context.observeUriChanges(uri: Uri): Flow<Unit> = callbackFlow {
val handler = Handler(Looper.getMainLooper())
val observer = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
trySend(Unit)
}
}
contentResolver.registerContentObserver(uri, false, observer)
awaitClose {
contentResolver.unregisterContentObserver(observer)
}
}
优势:使用
callbackFlow 后,Flow 取消时会自动执行 awaitClose 内的注销代码,无需手动配对。配合 viewModelScope 使用天然安全。
4.4 安全问题与避坑指南
- 线程安全:onChange 可能在非主线程调用(若注册时 Handler 为 null),此时更新 UI 需切换到主线程。推荐总是提供主线程 Handler。
- 泄漏风险:ContentObserver 持有外部引用(如 Activity/Fragment),若忘记注销会导致 Activity 无法 GC。必须做到注册→注销成对出现。
- 重复注册:同一个 ContentObserver 对象连续注册多次会导致重复通知。使用状态变量(如
observer != null)防止重复注册。 - Uri 混淆:权限变化对应的 Uri 可能因 Android 版本不同而不同,应使用
Settings.Secure.getUriFor(…)等标准 Api 获取。
版本兼容:Android 10+ 对后台权限监听有限制。若应用处于后台,onChange 可能不会触发。建议结合
ForegroundService 或 WorkManager 才能可靠监听。
4.5 调试与验证
- 使用
adb shell settings put global airplane_mode_on 1模拟设置变化(需对应 Uri)。 - 在 onChanged 中打日志:
Log.d("PermObserver", "change detected"),观察 logcat 输出。 - 在 Activity 的
onStop中注销,onStart中注册,验证泄漏检测工具(LeakCanary)无报告。
# 调试命令示例:监听蓝牙开关变化
adb shell settings put global bluetooth_on 0
# 日志输出应出现 "change detected"
通过本节内容,你已经掌握了 ContentObserver 的底层实现、Flow 化模式以及核心安全措施。下一环节将基于此构建高阶的权限状态同步组件。