混合开发中,如何统一管理 Native 与 Web 的权限请求?

解读

国内 90% 的混合应用采用“Native 壳 + Web 业务”架构,Web 侧通过 JSBridge 调用原生能力。Android 6.0 之后权限改为动态申请,Web 侧没有 Activity 无法直接调用系统 API,若各自为政会出现:

  1. 重复弹窗,用户体验割裂;
  2. 权限结果回调丢失,Web 侧无法继续流程;
  3. 国内 ROM 二次定制,权限逻辑差异大,分散处理容易遗漏适配。

因此面试官希望听到“一套中心化、可回溯、可降级、可灰度”的方案,而不是简单回答“用 onRequestPermissionsResult 就行”。

知识点

  1. Android 动态权限模型:ActivityCompat.requestPermissions + onRequestPermissionsResult + shouldShowRequestPermissionRationale。
  2. Web→Native 通信:WebViewClient.shouldOverrideUrlLoading、@JavascriptInterface、WebMessagePort、Kotlin 协程封装。
  3. 权限代理模式:Native 侧维护 PermissionDispatcher,统一收口所有权限请求,Web 侧仅声明“能力”而非“权限”。
  4. 生命周期安全:使用 ViewModel + SavedStateHandle 保存回调,防止因进程重启、WebView 销毁导致回调丢失。
  5. 国内合规:工信部 164 号文要求“用户同意前不得获取权限”,需先弹隐私弹窗,再调用系统权限;拒绝后需走“设置页引导”而非循环弹窗。
  6. 降级策略:当 Web 侧请求的能力在低端机无对应硬件时,返回 mock 数据或空实现,保证业务不断流。
  7. 灰度与埋点:通过 Matrix 或字节埋点 SDK 记录“申请-授权-拒绝”漏斗,后台实时关闭高风险权限的灰度开关。

答案

整体采用“三层两通道”方案:

  1. 能力声明层(Web) Web 业务方只声明需要的能力,例如 “scanQRCode”、“location”,前端不感知 Android 权限名。JSBridge 协议统一为: native.invoke('ability', {name: 'location', params: {}}, callbackId)

  2. 能力路由层(Native) Native 侧维护 AbilityRegistry,将能力映射到一组 Android 权限。例如: location → [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION] 若系统为 Android 12+,则额外追加 ACCESS_COARSE_LOCATION 的近似权限,保证后台定位场景合规。

  3. 权限调度层(Native) 单例 PermissionDispatcher 持有当前 Activity 弱引用,内部使用 Kotlin 协程封装请求流程:

    • 先检查权限:ContextCompat.checkSelfPermission
    • 若已授权,直接返回 GRANTED;
    • 若拒绝且 shouldShowRequestPermissionRationale 为 true,弹业务自定义教育弹窗(符合国内隐私合规);
    • 否则调用 ActivityResultLauncher<String[]> 发起系统弹窗;
    • 结果通过 SavedStateHandle 恢复,再经 callbackId 回传 Web 侧。
  4. 两通道

    • 正向通道:Web 调用 native 能力;
    • 反向通道:Native 通过 WebView.evaluateJavascript 把结果回传,格式为: window.bridge.onCallback(callbackId, {code: 0, data: {lat: 39.9, lng: 116.3}})
  5. 异常与重试 若用户永久拒绝,Dispatcher 记录到 DataStore,7 天内不再主动打扰;业务侧可通过“设置页引导”浮层二次教育。Web 侧收到 code=1 时,展示“去设置”按钮,点击后跳转到 Settings.ACTION_APPLICATION_DETAILS_SETTINGS。

  6. 性能与内存 使用 ViewModelScope 管理协程,WebView 销毁时自动取消;回调使用 WeakReference 防止泄漏。

一句话总结:Native 侧建立“能力-权限”映射表,通过中心化 Dispatcher 统一申请、缓存、回传,Web 侧仅关心能力结果,实现 0 耦合、0 丢失、0 重复弹窗。

拓展思考

  1. 鸿蒙 NEXT 不再兼容 AOSP,如果未来项目需要迁移至 ArkUI,如何复用这套“能力声明层”?
    可把 AbilityRegistry 抽成 Kotlin Multiplatform 模块,通过 ArkTS 的 NAPI 重新实现 PermissionDispatcher,协议字段保持不变,Web 侧无需改动。

  2. 小程序容器场景下,权限请求链路多一跳(小程序→宿主→系统),如何防止“双教育弹窗”?
    宿主侧维护“权限教育白名单”,同一能力 24 小时内只教育一次;小程序调用时先查白名单,命中则直接走系统弹窗。

  3. 折叠屏双窗体同时请求同一权限,如何避免系统弹窗重复?
    在 Dispatcher 内加“请求锁”,以权限组+用户 ID 为 key,首次请求未返回前,后续相同 key 直接复用同一个 ActivityResultLauncher,结果返回后广播给双窗体。

  4. 如果后续接入谷歌 Privacy Sandbox,AD_ID 权限变为“可撤销”,如何动态降级广告 SDK?
    在 AbilityRegistry 中增加“可撤销”标记,一旦系统广播 ACTION_APPLICATION_PERMISSIONS_CHANGED,立即重新校验,若撤销则通知广告 SDK 切换为 PSID 方案,保证收益不中断。