uni-app 全量权限:统一别名、归一状态与多端分流
uni-app 全量权限:统一别名、归一状态与多端分流
插件:lf-permission 1.0.0
地址:https://ext.dcloud.net.cn/plugin?id=29096
业务场景:相机、定位、相册、通知、悬浮窗等权限在 Android、iOS、鸿蒙、小程序、H5 上检测与申请方式不同。本文说明别名表、状态机、各端实现、用法与真机问题。
需求
三类能力:
- 检测:只读
check,不弹系统授权框 - 申请:
request/requestMany;特殊权限跳转设置页 - 组合:
ensure= 检测 → 申请 → 按需打开设置 → 再检测
主 API:
check(permission)
request(permission)
requestMany(permissions, { stopOnDenied })
ensure(permission, { request, openSettingOnDenied })
openSetting({ type, permission })
返回统一结构:
{
permission: 'camera',
status: 'granted', // granted | denied | permanentlyDenied | limited | serviceOff | unsupported | unknown
platform: 'android',
native: 'android.permission.CAMERA',
kind: 'runtime',
special: false,
canRequest: true,
supported: true,
message: 'granted'
}
状态含义:
| status | 含义 |
|---|---|
granted |
已授权 |
denied |
未授权 |
permanentlyDenied |
永久拒绝 /「不再询问」 |
limited |
有限授权(iOS 部分相册) |
serviceOff |
系统开关关闭 |
unsupported |
当前端无此项 |
unknown |
无法判断 |
平台差异
| 环境 | 行为 |
|---|---|
| Android | plus.android.requestPermissions;API 33+ 使用 READ_MEDIA_* / POST_NOTIFICATIONS;悬浮窗等特殊权限走 Intent |
| iOS | uni.getAppAuthorizeSetting + 原生 authorization / request;永久拒绝后打开 App 设置 |
| 鸿蒙 | uni 授权查询与 openAppAuthorizeSetting |
| 小程序 | getSetting / authorize / openSetting,别名映射 scope.* |
| H5 | Permissions API / getUserMedia / Notification;openSetting 返回 false |
| nvue | 不依赖 document 监听 plusready,轮询 plus 就绪 |
| uni-app x | UTS 代码在 utssdk.pending;Vue / App 使用 index.js |
处理流程:
别名 / 原生权限串
│
├─ android ── runtime 申请 / special 跳设置
├─ ios ────── AppAuthorize + 原生 request
├─ harmony ── uni 授权 API
├─ mp-* ───── scope authorize
└─ web ────── Permissions / getUserMedia
权限种类:
| kind | 含义 | 行为 |
|---|---|---|
runtime |
运行时权限 | request 弹系统框 |
special |
特殊权限 | 只 check + openSetting |
service |
系统开关 | 如定位服务;关闭为 serviceOff |
privacy |
隐私权限 | iOS 首次触发系统弹框 |
引入
import {
check,
request,
requestMany,
ensure,
openSetting,
isSupported,
listPermissions,
getPlatform,
PermissionStatus,
SettingType
} from '@/uni_modules/lf-permission/index.js'
使用 index.js 入口。
插件不写入工程权限声明。用到哪项,在 manifest / iOS Privacy / 小程序后台声明哪项。
用法
只读检测
const ret = await check('camera')
if (ret.status === PermissionStatus.GRANTED) {
// 已授权
} else if (ret.status === PermissionStatus.UNSUPPORTED) {
// 当前端无此项
} else {
// 未授权
}
申请与永久拒绝
const ret = await request('location')
if (ret.status === PermissionStatus.GRANTED) {
// 继续
} else if (ret.status === PermissionStatus.PERMANENTLY_DENIED) {
await openSetting({ permission: 'location' })
}
ensure
const ret = await ensure('microphone', {
request: true,
openSettingOnDenied: true
})
if (ret.status !== PermissionStatus.GRANTED) {
uni.showToast({ title: '需要麦克风权限', icon: 'none' })
return
}
定位:先系统开关,再 App 权限
系统定位开关与 App 定位权限是两层状态,分开检测:
async function getMyLocation() {
let service = await check('locationService')
if (service.status === PermissionStatus.SERVICE_OFF) {
await openSetting({ type: SettingType.LOCATION_SERVICE })
service = await check('locationService')
if (service.status !== PermissionStatus.GRANTED) {
uni.showToast({ title: '请开启系统定位', icon: 'none' })
return
}
}
const appLoc = await ensure('location', {
request: true,
openSettingOnDenied: true
})
if (appLoc.status !== PermissionStatus.GRANTED) {
uni.showToast({ title: '需要定位权限', icon: 'none' })
return
}
uni.getLocation({
type: 'gcj02',
success: (res) => {
console.log(res.latitude, res.longitude)
}
})
}
从设置页返回后复查
特殊权限打开设置后,授权结果在用户返回时才变化。在 onShow 再 check:
export default {
data() {
return { waiting: '' }
},
async onShow() {
if (!this.waiting) return
const ret = await check(this.waiting)
this.waiting = ''
// 按 ret.status 继续业务
},
methods: {
async askOverlay() {
this.waiting = 'overlay'
await openSetting({ permission: 'overlay' })
}
}
}
打开设置类型
await openSetting({ type: SettingType.APP })
await openSetting({ type: SettingType.LOCATION_SERVICE })
await openSetting({ type: SettingType.NOTIFICATION })
await openSetting({ type: SettingType.OVERLAY })
await openSetting({ type: SettingType.BATTERY })
await openSetting({ type: SettingType.INSTALL })
await openSetting({ type: SettingType.MANAGE_STORAGE })
权限别名(部分)
完整表见 readme.md / permissions.js。业务使用别名;也支持直接传 android.permission.XXX、scope.xxx、ohos.permission.XXX。
| 别名 | 说明 | kind | Android | iOS / 小程序 |
|---|---|---|---|---|
location |
精确定位 | runtime | ACCESS_FINE_LOCATION |
WhenInUse / scope.userLocation |
locationService |
系统定位开关 | service | 系统 Location | 系统开关 / locationEnabled |
camera |
相机 | runtime | CAMERA |
camera / scope.camera |
microphone |
麦克风 | runtime | RECORD_AUDIO |
record / scope.record |
photoRead |
读相册/媒体 | runtime | API 33+ READ_MEDIA_* |
photoLibrary |
notification |
通知 | runtime | API 33+ POST_NOTIFICATIONS |
通知授权 |
overlay |
悬浮窗 | special | SYSTEM_ALERT_WINDOW |
仅 Android |
bluetooth |
蓝牙 | runtime | API 31+ BLUETOOTH_* |
bluetooth / scope.bluetooth |
manageExternalStorage |
所有文件访问 | special | MANAGE_EXTERNAL_STORAGE |
审核风险高 |
电话、短信、通话记录等敏感权限在别名表中。无业务需求时不要在 manifest 声明。
实现要点
Android 运行时权限
plus.android.requestPermissions(
natives,
(resultObj) => {
// granted / deniedPresent / deniedAlways
// deniedAlways → permanentlyDenied
},
(error) => { /* unknown */ }
)
检测使用 ContextCompat.checkSelfPermission;不可用时回退 support 包或 checkSelfPermission。
媒体与存储按 SDK_INT 择权:
- API 33+:
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO/POST_NOTIFICATIONS - 更低:
READ_EXTERNAL_STORAGE
Android 特殊权限
Settings.canDrawOverlays(main)
// 未授权 → ACTION_MANAGE_OVERLAY_PERMISSION
同类:writeSettings、batteryOptimization、installPackages、manageExternalStorage、scheduleExactAlarm、notificationListener。request 对这些别名打开设置页,不调用 requestPermissions。
iOS
读取 uni.getAppAuthorizeSetting() 字段(cameraAuthorized、locationAuthorized 等)。
字段不足时使用原生 authorizationStatus。request 触发对应 request*Authorization;状态为 permanentlyDenied 时打开 app-settings:。
系统定位开关与 App 定位权限分离:locationService 与 location。
小程序
uni.getSetting → authSetting[scope]
uni.authorize({ scope })
uni.openSetting() // 永久拒绝后
别名 location → scope.userLocation,camera → scope.camera。
nvue / plus 就绪
// 有 document:监听 plusready
// 无 DOM:轮询 typeof plus !== 'undefined'
各端声明(部分)
Android(用到再加):
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
iOS Privacy Key(未填用途文案时系统拒绝授权):
| 用途 | Key |
|---|---|
| 相机 | NSCameraUsageDescription |
| 麦克风 | NSMicrophoneUsageDescription |
| 相册 | NSPhotoLibraryUsageDescription |
| 定位 | NSLocationWhenInUseUsageDescription |
真机问题
-
永久拒绝后继续弹授权框无效
状态为permanentlyDenied。调用openSetting打开设置页。 -
App 定位已授权仍拿不到坐标
系统定位总开关为serviceOff。先check('locationService')。 -
iOS 跳转定位设置无反应
私有App-Prefs在高版本受限。回退app-settings:/uni.openAppAuthorizeSetting。 -
Android 13 读相册失败
使用别名photoRead,插件按 API 选择READ_MEDIA_*。 -
通知权限在 Android 12 申请无效果
POST_NOTIFICATIONS从 API 33 起才是运行时权限。更低版本检测通知开关,打开通知设置页。 -
悬浮窗
request后立刻check仍是 denied
用户还在设置页。在onShow复查。 -
导入报 UTS 编译失败
使用@/uni_modules/lf-permission/index.js。UTS 代码位于utssdk.pending。 -
nvue 中
plus为 undefined
未等待 plus 就绪。插件内部轮询;业务侧对权限 API 使用await。 -
小程序
authorize失败且无法再弹
状态为permanentlyDenied,调用openSetting。 -
H5
openSetting为 false
浏览器无法打开站点权限页。用文案引导用户到地址栏站点设置。 -
声明了电话权限但业务未用
应用市场上架被拒。无需求不要写入phone/sms等权限。 -
manageExternalStorage上架失败
所有文件访问属敏感特殊权限。无强需求不要使用。 -
后台定位申请失败
Android 10+ 须先具备前台定位,再申请locationBackground。 -
业务用数字码判断权限
使用PermissionStatus字符串比较,例如ret.status === PermissionStatus.GRANTED。
排查
console.log(getPlatform())
console.log(await check('camera'))
console.log(listPermissions({ onlyCurrent: true }))
检查项:
- 当前端
platform与预期是否一致 ret.native是否为当前 API 应申请的权限串- manifest / plist / 小程序是否已声明
status是denied还是permanentlyDenied/serviceOff- 特殊权限是否在
onShow复查 - 引入路径是否为
index.js
Demo:/uni_modules/lf-permission/pages/demo/demo
逐项「检查 / 申请 / ensure / 设置」。
源码目录
uni_modules/lf-permission/
├── index.js # 对外 API
├── constants.js # 状态 / SettingType
├── permissions.js # 别名全表
├── core.js # 结果构造、平台探测
├── platforms/
│ ├── android.js
│ ├── ios.js
│ ├── harmony.js
│ ├── mp.js
│ └── web.js
├── pages/demo/demo.vue
├── utssdk.pending/ # uni-app x(启用时改名为 utssdk)
├── readme.md
├── changelog.md
└── article.md
接入要求:
- 插件目录为
uni_modules/lf-permission - 从
@/uni_modules/lf-permission/index.js引入 - 按业务声明各端权限与 iOS 用途文案
- 定位场景区分
locationService与location - 特殊权限在设置返回后复查
- 敏感权限无需求不声明
调用示例:
await ensure('camera', {
request: true,
openSettingOnDenied: true
})
问题反馈
更多推荐



所有评论(0)