更新记录

1.0.0(2026-08-20)

  • Android API 29 后台定位申请补前台定位权限组合:Android 10 强制后台定位与前台定位同一请求申请,单独申请被系统静默拒绝(修复后 backgroundLocation 在 sdk==29 时同请求 ACCESS_BACKGROUND_LOCATION + ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION)。
  • Android 拒绝回调保留永久拒绝判定requestPermission 拒绝路径直接采用 hasPermission 权威判定,deniedAlways 不再被清空,避免连拒两次后误报"可再次请求"。
  • iOS 通知授权状态改缓存+后台刷新hasPermission('notification') 原实现用 DispatchSemaphore 阻塞主线程,存在死锁并恒误判 PERMISSION_NOT_DETERMINED;改为同步读缓存、后台异步刷新,App 回前台自动刷新,请求成功后强制刷新。
  • *iOS .ephemeral 增加 `#available(iOS 14.0, )` 守卫**:deployment target 13.0 下编译兼容。
  • 代码审查优化:jsonString\b \f 与控制字符转义;getPrivacyStateJson 成功/失败返回结构统一(均带 code);提取 noRuntimePermissionResult / callPhoneUnsupportedResult / callLogUnsupportedResult / unsupportedTypeResult 公共结果函数,消除 hasPermission / requestPermission 重复分支。
  • 隐私清单说明:插件不采集用户数据(仅查询授权状态 + UserDefaults 存同意状态),PrivacyInfo.xcprivacy 增加注释;宿主授权后自行采集位置/运动健康等数据时须在宿主隐私清单申报。
  • iOS 编译机制结论:iOS 运行产物为 HBuilderX 标准基座内 Frameworks/unimodulePrivacyCompliance.framework,由 HBuilderX 每次运行 iOS 时重新编译;真机/模拟器经 uniapp-launcher 自定义基座(离线 SDK 5.24 运行时)验证通过。
  • iOS 照片状态/请求函数合并photoReadStatus/photoAddStatus/requestPhotoRead/requestPhotoAdd 合并为 photoStatus(readAccess:)/requestPhoto(readAccess:...)蓝牙/定位/通知授权请求统一 5s 超时兜底(回调完成自动取消,超时按当前授权状态给结论),杜绝 Promise 永久 pending;readme 补充 iOS 结果字段平台差异(deniedPresentdenied 一致、restricted 合并 PERMISSION_DENIED_ALWAYS、iOS 13.0 蓝牙降级、REQUEST_PERMISSION_TIMEOUT 语义)。
  • requestPermission 前置可申请性校验(与 hasPermission 口径统一):明确不可申请的状态(已废弃/平台不支持/需前置/清单缺失)直接透传 hasPermission 结论,避免申请平台不支持的权限被底层权限库拒绝/抛异常;hasPermission 对低版本媒体入口(photo/video/audio/album,sdk<33)复用存储废弃判定;拒绝/doNotAskAgain 回调整体 try/catch 兜底 resolve PERMISSION_REQUEST_EXCEPTION;iOS 原生 completion 全部统一主线程派发(照片/通知/通讯录/日历/相机/麦克风/蓝牙/活动识别)。
  • iOS backgroundLocation 两段式弹窗处理requestAlwaysAuthorization 用户先同意 WhenInUse 时不提前完成,等待第二段"升级为始终"或 5s 超时给出 BACKGROUND_LOCATION_UPGRADE_REQUIRED;蓝牙/活动识别 helper 回调身份比较(并发请求不被误清,杜绝 Promise 永久 pending)。
  • Android requestPermission 前置可申请性校验排除部分授权:M-1 校验对 AUTHORIZED_LIMITED 视觉媒体放行(Android 14+ 部分授权后"调整范围"二次申请不被短路,继续弹系统选择器)。
  • Android 低版本媒体入口存储废弃判定修复photo/video/audio/album 在设备 sdk<33 + 宿主 targetSdk>=33 时按存储废弃处理(返回 LEGACY_READ_STORAGE_DEPRECATED),与 isPermissionSupported 口径一致(原判定复用 isReadStorageDeprecated 对 media type 恒 false,属死分支,已修正)。
  • Android 申请异常回滚"已申请"标记requestSystemPermission 同步抛异常(弹窗未出现)时回滚标记,避免误判永久拒绝;doNotAskAgain 回调改为复用 hasPermission 权威判定(多权限边界语义一致)。
  • iOS 隐私清单内置启用:插件随附 nativeResources/ios/PrivacyInfo.xcprivacy(UserDefaults CA92.1,不采集数据声明;移除无对应 API 的 DiskSpace 申报)。
  • 修复 Android hasPermission 无法识别「永久拒绝」(deniedAlways)导致连拒两次后页面仍显示「申请权限」:新增已申请权限记录 + shouldShowRequestPermissionRationale 判定,返回 PERMISSION_DENIED_ALWAYS / denied_forever / action=settings。(Android 6.0+ 权限弹窗勾选「不再询问」或 Android 11+ 连拒两次后,系统固定该权限,shouldShowRequestPermissionRationale 返回 false)
  • 权限 API 统一改为字符串式:requestPermission('camera')hasPermission('camera')getAndroidPermissions('camera')isPermissionSupported('camera')openPermissionSettings('camera');移除对象式 { type } 传参与 name/type 双层读取存储细分。
  • 修复 Android getPrivacyStateJson() 手动拼接 JSON 的转义风险,统一隐私版本号 trim 规范化。
  • 调整双端 hasPermission 语义:未同意隐私政策时仍可查询系统当前授权状态;requestPermission 继续受 enforceAgreed 拦截并返回 PRIVACY_NOT_AGREED
  • iOS 隐私版本号读写增加 trim 规范化,避免空格导致已同意状态失效。
  • Android 关键异常路径增加 console.warn 日志,减少静默吞错。
  • 更新文档与平台能力矩阵,明确 hasPermissionrequestPermission 的隐私同意边界。
  • Android 14+ / 16 适配 Selected Photos Access:photo / video / album 支持部分照片和视频授权,返回 AUTHORIZED_LIMITED,并避免部分授权后误报未授权或 Manifest 缺失。
  • Demo 与公开权限矩阵移除独立 selected-media 入口;底层保留 visualUserSelected 兼容类型用于系统能力适配。
  • 上线自检清单补充 Android 9-16 存储 / 媒体权限版本差异、Android 16 真机专项记录和照片视频权限上架留证项。

平台兼容性

uni-app(3.8.2)

Vue2 Vue3 Chrome Safari app-vue app-vue插件版本 app-nvue app-nvue插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙
× × × × 1.0.0 1.0.0 5.0 1.0.0 13 1.0.0 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

uni-app x(4.0)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × 5.0 13 × ×

隐私合规接入助手 privacy-compliance

UTS 原生 uni-app 隐私合规插件:原生隐私同意状态、Android/iOS 权限申请、隐私弹窗组件与上架前静态扫描。

⚠️ 重要提示

  • 本插件用于辅助规范隐私合规流程,不构成法律意见,也不承诺 100% 通过任何应用市场审核。
  • 本插件不默认新增宿主权限。 安装本插件不会自动向宿主 manifest 注入业务权限;宿主 App 需按实际需求自行声明、裁剪并披露权限。
  • Demo 项目privacy-demo)为全权限覆盖测试用,manifest 声明了 37 个 Android 权限(含 14 个高风险/强审核权限:后台定位、读取手机状态、所有文件访问、悬浮窗、安装未知应用、精确闹钟、全屏通知、查询已安装应用列表、后台身体传感器、读取通话记录、读取手机号、读取/接收/发送短信)。不可将 Demo manifest 直接用于生产上架
  • 正式项目接入时,请严格按实际业务需求裁剪 manifest,仅保留必要权限,并在隐私政策中逐一说明。

架构

层级 说明
UTS 原生 隐私同意读写、权限查询/申请、打开设置(import * as PC from '@/uni_modules/privacy-compliance'
privacy-flow 组件 可选 Vue 封装:隐私弹窗 + 调用 UTS agree/reject
privacy-dialog 纯 UI 弹窗,由 privacy-flow 或业务方自行驱动
privacy-scan.js Node 静态扫描(Android Manifest + iOS plist + SDK 痕迹)

不再提供 js_sdk/index.js。业务侧 SDK 延迟初始化请在同意回调中自行执行。

快速接入

<template>
  <privacy-flow
    app-name="我的App"
    privacy-version="2026-06-15"
    privacy-url="https://example.com/privacy"
    agreement-url="https://example.com/agreement"
    @agree="initThirdPartySdks"
    @ready="initThirdPartySdks"
  />
</template>

<script>
import PrivacyFlow from '@/uni_modules/privacy-compliance/components/privacy-flow/privacy-flow.vue'
import * as PrivacyNative from '@/uni_modules/privacy-compliance'

export default {
  components: { PrivacyFlow },
  methods: {
    initThirdPartySdks() {
      if (!PrivacyNative.hasAgreed()) return
      // 初始化统计、推送等第三方 SDK
    }
  }
}
</script>

UTS 原生 API(Android / iOS 统一)

import * as PC from '@/uni_modules/privacy-compliance'

// 隐私同意
PC.initPrivacy('2026-06-15', true)
PC.hasAgreed()
PC.agree('2026-06-15')
PC.rejectPrivacy()
PC.withdrawConsent()
PC.resetPrivacyState()
PC.getPrivacyStateJson()   // 调试:返回 JSON 字符串

// 权限(双端相同字符串式 API)
 PC.getAndroidSdkInt()      // iOS 固定返回 0
 PC.getAndroidPermissions('camera')
 PC.isPermissionSupported('camera')  // 当前平台/版本是否支持申请该 type
 PC.hasPermission('camera')
 await PC.requestPermission('camera')  // 用途说明弹窗由业务在调用前自行展示
 PC.openPermissionSettings('notification')
 PC.openAppSettings()

hasPermissionJson / requestPermissionJson 仍保留在 iOS 实现内部,不再作为公开导出;请统一使用 hasPermission / requestPermission

默认 enforceAgreed=false(UTS initPrivacy 参数默认值):initPrivacy 未调用时权限申请不被隐私协议拦截;需要“未同意隐私即拦截权限申请”时,显式传入 enforceAgreed=true(此时 requestPermission 返回 PRIVACY_NOT_AGREED 且不弹系统权限框;hasPermission 仍可查询系统授权状态)。注意:privacy-flow 组件的 enforceAgreed prop 默认 true(组件场景默认拦截),与 UTS API 默认值不同,集成时按需显式指定。

权限申请前的用途说明弹窗uni.showModal)由业务在调用 requestPermission 前自行展示,Demo 中有示例。

Android 权限类型映射

type Android 权限
camera CAMERA
location ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
backgroundLocation ACCESS_BACKGROUND_LOCATION
record / microphone RECORD_AUDIO
notification Android 13+ POST_NOTIFICATIONS
photo Android 13+ READ_MEDIA_IMAGES,Android 14+ 同时使用 READ_MEDIA_VISUAL_USER_SELECTED 表示部分授权;Demo 在 Android 13 以下不展示该公开入口
video Android 13+ READ_MEDIA_VIDEO,Android 14+ 同时使用 READ_MEDIA_VISUAL_USER_SELECTED 表示部分授权;Demo 在 Android 13 以下不展示该公开入口
audio Android 13+ READ_MEDIA_AUDIO;Demo 在 Android 13 以下不展示该公开入口
album / media 图片+视频组合申请入口,不是额外系统权限;Android 13+ 图片+视频,Android 14+ 支持 AUTHORIZED_LIMITED;Demo 在 Android 13 以下不展示该公开入口
storage / readStorage 旧版读取存储兼容入口;映射 READ_EXTERNAL_STORAGE可用性同时受宿主 targetSdk 与设备 sdk 影响:宿主 targetSdk>=33 或设备 sdk>=33 时判不支持(targetSdk>=33 时 XXPermissions 强制禁止申请 READ_EXTERNAL_STORAGE,设备 sdk>=33 时媒体改走 READ_MEDIA_*)。Android 13+ 读取媒体请使用 photo / video / audio / album 入口,插件自动适配 READ_MEDIA_*
writeStorage 旧版写入存储兼容入口;Android 9 及以下(设备 sdk<=28)映射 WRITE_EXTERNAL_STORAGE 可申请;Android 10+ 应使用分区存储、MediaStore、SAF 或应用私有目录
manageStorage 所有文件访问:Android 11+ 为 MANAGE_EXTERNAL_STORAGE 特殊设置权限;Android 9 及以下映射旧版 READ/WRITE_EXTERNAL_STORAGE;Android 10 且宿主 targetSdk>=30 时判不支持(分区存储强制)
bluetooth Android 12+ BLUETOOTH_SCAN, BLUETOOTH_CONNECT
更多 permission-matrix.json

Android 13 以下没有公开的 photo / video / audio / album 媒体拆分入口;Android 13+ 分别映射 READ_MEDIA_IMAGES / READ_MEDIA_VIDEO / READ_MEDIA_AUDIOreadStorage 仅在宿主 targetSdk<=32 且设备 sdk<33 时可用(映射 READ_EXTERNAL_STORAGE);宿主 targetSdk>=33 时被 XXPermissions 强制禁止申请该权限,判不支持。writeStorage 在 Android 9 及以下(设备 sdk<=28)可用;Android 10+ 的业务文件流程走 MediaStore、SAF、系统选择器或应用私有目录。

Android 14+ 的“部分照片和视频”不是独立业务入口:宿主仍通过 photovideoalbum 发起申请,插件用 AUTHORIZED_LIMITED 表示用户只授权了部分媒体。Demo 不再单独展示 selected-media 入口,visualUserSelected 不作为公开 supported type;底层仅保留 Manifest / Android native 兼容处理。

正式上架时请按业务选择媒体方案:如果核心功能需要持续或频繁访问图库,可保留 READ_MEDIA_IMAGES / READ_MEDIA_VIDEO 并准备权限用途说明、页面截图、隐私政策和应用市场声明;如果只是头像上传、发帖选图、一次性导入等偶发访问,优先使用 Android Photo Picker、SAF 或系统选择器,并移除广泛媒体库读取权限。

iOS 权限类型映射

type iOS 实现
camera NSCameraUsageDescription
photo / album / media / video NSPhotoLibraryUsageDescription(iOS 14+ 可能返回 AUTHORIZED_LIMITED;limited 无法通过再次请求升级,需引导设置页调整范围)
storage / readStorage IOS_PLATFORM_NOT_AVAILABLE(iOS 无共享存储权限,读文件请用 UIDocumentPicker,读媒体用 photo/video
audio IOS_PLATFORM_NOT_AVAILABLE(请用 UIDocumentPicker 或 album
writePhoto / saveAlbum / writeStorage NSPhotoLibraryAddUsageDescription
record / microphone NSMicrophoneUsageDescription
location NSLocationWhenInUseUsageDescription
backgroundLocation NSLocationAlwaysAndWhenInUseUsageDescription
notification UNUserNotificationCenter
contacts NSContactsUsageDescription
calendar NSCalendarsUsageDescription
bluetooth NSBluetoothAlwaysUsageDescription(iOS 13+)
activityRecognition NSMotionUsageDescription
callPhone 无需运行时权限(NO_RUNTIME_PERMISSION_REQUIRED
manageStorage / installPackages / overlay / exactAlarm Android 专有(PLATFORM_ANDROID_ONLY
phone / phoneNumbers / sms / callLog / bodySensors iOS 无通用 API(IOS_PLATFORM_NOT_AVAILABLE
packages / fullScreenIntent 审核/配置项(SPECIAL_PERMISSION_REVIEW_REQUIRED

全部 Android type 在 iOS 侧均有对应 API 语义,详见 templates/platform-capability-matrix.md

Android 已知行为(平台限制,非插件缺陷):

  • "去设置"页返回后按钮变回"申请权限":Android 系统在用户从设置页返回后会重置 shouldShowRequestPermissionRationale 为 true,插件无法区分"已永久拒绝"与"可再次申请"(无公开 API 读取永久拒绝标记)。连拒两次后按钮显示"去设置";从设置页返回后可能变回"申请权限",再次点击时系统可能不弹窗(已固定为不再询问)。如需保持"去设置",业务方可自行持久化拒绝状态。
  • Android 14+ photo 全量授权后 video/album 显示"调整范围":Photo Picker 的"全部照片和视频"仅授予请求中的权限(photo 只请求 READ_MEDIA_IMAGES),READ_MEDIA_VISUAL_USER_SELECTED 无法区分全量/部分,插件保守显示"调整范围";点击后系统依据已记录的全量选择自动补授 VIDEO(不弹窗)并变"已授权"。
  • iOS 通知授权首次查询hasPermission('notification') 基于缓存(App 回前台自动刷新、请求后强制刷新),冷启动后首次查询可能返回 PERMISSION_NOT_DETERMINED,下次调用即最新。
  • iOS 后台定位两段式:申请"始终允许"时用户先同意 WhenInUse 会等待第二段弹窗;超时(5s)未响应返回 BACKGROUND_LOCATION_UPGRADE_REQUIREDaction=settings)。

iOS 结果字段的平台差异(与 Android 对齐口径):

  • deniedPresent:iOS 无法区分「可再次请求的拒绝」与「永久拒绝」(系统不提供 shouldShowRequestPermissionRationale 等价 API),deniedPresentdenied 保持一致;PERMISSION_DENIED_ALWAYSdeniedAlways 返回被拒权限列表。
  • restricted(家长控制 / MDM 限制):iOS 与 denied 合并返回 PERMISSION_DENIED_ALWAYSaction=settings),宿主引导设置页在受限场景下可能无效,属系统限制。
  • 蓝牙:iOS 13.0 无读取授权状态的公开 API(CBManager.authorization 需 13.1+),按 NO_RUNTIME_PERMISSION_REQUIRED 处理(弹窗仍由系统首次创建 CBCentralManager 自动触发),建议在 iOS 13.1+ 真机验证实际授权状态。
  • 超时兜底:蓝牙 / 定位 / 通知的授权请求 5 秒无回调时返回 REQUEST_PERMISSION_TIMEOUT(不会永久 pending);活动识别沿用既有 5s 超时。

iOS openPermissionSettings

type 行为
notification iOS 16+ 打开应用通知设置;低版本回退应用设置页
其他已支持 type 打开应用设置页(iOS 无 Android 式按权限独立 Settings Intent)

平台支持范围

平台 UTS 原生 API privacy-flow 弹窗 UI
App-Android ✅(建议 minSdk 21+)
App-iOS ✅(Deployment Target 13.0+)
H5 ⚠️ 仅 UI,无原生同意状态/权限
小程序 ⚠️ 仅 UI,无原生同意状态/权限

插件市场声明中的“插件不默认新增权限”表示:安装插件不会自动向宿主 manifest 注入业务权限;本插件只提供隐私同意与权限申请能力,宿主 App 仍需按实际业务自行声明、裁剪并披露权限。

H5 / 小程序不提供 JS fallback。 若多端共用,请业务侧自行处理隐私状态与权限降级,或仅在 App 端静态 import 本插件。

权限矩阵

修改 permission-matrix.json 后执行:

node uni_modules/privacy-compliance/scripts/sync-permission-matrix.js

同步 templates/permission-matrix.mdtemplates/android-permission-risk-rules.mdtemplates/platform-capability-matrix.md

发版前建议执行上述命令,确保 UTS 实现与矩阵无 drift。

iOS Privacy Manifest

App Store 要求提供 PrivacyInfo.xcprivacy。可复制模板并按业务填写:

cp uni_modules/privacy-compliance/templates/PrivacyInfo.xcprivacy.template <你的 iOS 原生资源目录>/PrivacyInfo.xcprivacy

privacy-scan.js 会检测项目中是否包含该文件。

静态扫描

node uni_modules/privacy-compliance/scripts/privacy-scan.js <项目根目录> \
  --json privacy-scan-report.json \
  --md privacy-scan-report.md

扫描 Android 权限、iOS plistcmds/Info.plist、Privacy Manifest、常见 SDK 痕迹。Demo 全权限测试台会产生高风险权限 WARN,这是覆盖测试预期;正式项目应裁剪权限后重新扫描。

privacy-flow 组件 Props

Prop 说明
appName 应用名,替换弹窗 {appName}
privacyVersion 隐私政策版本,变化后需重新同意
privacyUrl / agreementUrl 协议外链
privacyPage / agreementPage 项目内协议页路径
enforceAgreed UTS initPrivacy 默认 falseprivacy-flow 组件 prop 默认 true(组件场景默认在未同意时拦截权限申请)。需要拦截时传 true,此时 requestPermission 返回 PRIVACY_NOT_AGREED 且不弹系统权限框;hasPermission 仍可查询系统授权状态
rejectAction exit(默认)或 stay
dialog 弹窗文案与样式
autoShow 挂载时自动 ensureAgreed

方法:ensureAgreed()withdrawConsent(showDialog)resetPrivacyState()

接入检查清单

  • 隐私弹窗在任何 SDK 初始化前出现
  • 第三方 SDK 仅在 @agree / @readyhasAgreed() 为真后初始化
  • 权限申请发生在用户触发相关功能时,并说明用途
  • 隐私政策更新时同步提升 privacyVersion

后续路线

  • 鸿蒙平台增强
  • 自动化测试与 CI 回归

隐私、权限声明

1. 本插件需要申请的系统权限列表:

插件不默认新增权限;宿主 App 请按实际业务在 manifest 中声明并裁剪权限。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

本插件不采集或上传用户数据;仅在宿主 App 本地保存隐私同意状态。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。