更新记录
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 结果字段平台差异(deniedPresent与denied一致、restricted合并PERMISSION_DENIED_ALWAYS、iOS 13.0 蓝牙降级、REQUEST_PERMISSION_TIMEOUT语义)。 - requestPermission 前置可申请性校验(与 hasPermission 口径统一):明确不可申请的状态(已废弃/平台不支持/需前置/清单缺失)直接透传
hasPermission结论,避免申请平台不支持的权限被底层权限库拒绝/抛异常;hasPermission对低版本媒体入口(photo/video/audio/album,sdk<33)复用存储废弃判定;拒绝/doNotAskAgain回调整体 try/catch 兜底 resolvePERMISSION_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日志,减少静默吞错。 - 更新文档与平台能力矩阵,明确
hasPermission与requestPermission的隐私同意边界。 - 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_AUDIO。readStorage 仅在宿主 targetSdk<=32 且设备 sdk<33 时可用(映射 READ_EXTERNAL_STORAGE);宿主 targetSdk>=33 时被 XXPermissions 强制禁止申请该权限,判不支持。writeStorage 在 Android 9 及以下(设备 sdk<=28)可用;Android 10+ 的业务文件流程走 MediaStore、SAF、系统选择器或应用私有目录。
Android 14+ 的“部分照片和视频”不是独立业务入口:宿主仍通过 photo、video 或 album 发起申请,插件用 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_REQUIRED(action=settings)。
iOS 结果字段的平台差异(与 Android 对齐口径):
deniedPresent:iOS 无法区分「可再次请求的拒绝」与「永久拒绝」(系统不提供shouldShowRequestPermissionRationale等价 API),deniedPresent与denied保持一致;PERMISSION_DENIED_ALWAYS时deniedAlways返回被拒权限列表。restricted(家长控制 / MDM 限制):iOS 与denied合并返回PERMISSION_DENIED_ALWAYS(action=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.md、templates/android-permission-risk-rules.md 与 templates/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 默认 false;privacy-flow 组件 prop 默认 true(组件场景默认在未同意时拦截权限申请)。需要拦截时传 true,此时 requestPermission 返回 PRIVACY_NOT_AGREED 且不弹系统权限框;hasPermission 仍可查询系统授权状态 |
rejectAction |
exit(默认)或 stay |
dialog |
弹窗文案与样式 |
autoShow |
挂载时自动 ensureAgreed |
方法:ensureAgreed()、withdrawConsent(showDialog)、resetPrivacyState()
接入检查清单
- 隐私弹窗在任何 SDK 初始化前出现
- 第三方 SDK 仅在
@agree/@ready或hasAgreed()为真后初始化 - 权限申请发生在用户触发相关功能时,并说明用途
- 隐私政策更新时同步提升
privacyVersion
后续路线
- 鸿蒙平台增强
- 自动化测试与 CI 回归

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 27
赞赏 0
下载 12518864
赞赏 1943
赞赏
京公网安备:11010802035340号