4. 实现监听权限的底层 ContentObserver:注册与注销、回调转 Flow、安全问题

ContentObserver 是 Android 监听系统设置(包括权限)变化的底层回调机制。本章将拆解它的注册与注销生命周期、如何将回调转换为结构化 Flow,以及必须防范的线程与泄漏安全问题。

4.1 核心原理与类图

Android 权限变更通过 Settings.System、Settings.Secure、Settings.Global 以及 AppOpsManager 的 Uri 通知。ContentObserver 观察指定 Uri,当内容变化时触发 onChange。其本质是 Binder 驱动的进程间回调,运行在注册时指定的 Handler 线程上。

关键类
  • ContentObserver(Handler?) – 抽象基类,须重写 onChange
  • ContentResolver.registerContentObserver(Uri, boolean, ContentObserver) – 注册
  • ContentResolver.unregisterContentObserver(ContentObserver) – 注销
易错点:第二个参数 notifyForDescendants 设为 true 会同时监听子 Uri 变化;对于精确权限 Uri(如 package:)应设为 false 避免重复触发。
ContentObserver 注册–回调–注销流程 App 注册 ContentObserver ContentResolver 注册 ContentService 分发通知 onChange() 回调执行 Callback → Flow 转换 unregisterContentObserver 必须在 Lifecycle 或 ViewModelScope 中配对注册与注销,避免观察者泄漏

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()
    }
}
方法触发时机线程注意事项
registerContentObserverApp 主动调用任意线程指定 Handler 为 null 则回调在 Binder 线程池
onChange系统设置变更注册时指定的 Handler 线程不要在 onChange 中做耗时操作
unregisterContentObserverApp 主动调用任意线程必须在注册时相同的 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 调试与验证

  1. 使用 adb shell settings put global airplane_mode_on 1 模拟设置变化(需对应 Uri)。
  2. 在 onChanged 中打日志:Log.d("PermObserver", "change detected"),观察 logcat 输出。
  3. 在 Activity 的 onStop 中注销,onStart 中注册,验证泄漏检测工具(LeakCanary)无报告。
# 调试命令示例:监听蓝牙开关变化
adb shell settings put global bluetooth_on 0
# 日志输出应出现 "change detected"

通过本节内容,你已经掌握了 ContentObserver 的底层实现、Flow 化模式以及核心安全措施。下一环节将基于此构建高阶的权限状态同步组件。