ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

uni-app 全量权限:统一别名、归一状态与多端分流

uni-app 全量权限:统一别名、归一状态与多端分流

uni-app 全量权限:统一别名、归一状态与多端分流

插件:lf-permission1.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 | unknownplatform:'android',native:'android.permission.CAMERA',kind:'runtime',special:false,canRequest:true,supported:true,message:'granted'}

状态含义:

status含义
granted已授权
denied未授权
permanentlyDenied永久拒绝 /「不再询问」
limited有限授权(iOS 部分相册)
serviceOff系统开关关闭
unsupported当前端无此项
unknown无法判断

平台差异

环境行为
Androidplus.android.requestPermissions;API 33+ 使用READ_MEDIA_*/POST_NOTIFICATIONS;悬浮窗等特殊权限走 Intent
iOSuni.getAppAuthorizeSetting+ 原生 authorization / request;永久拒绝后打开 App 设置
鸿蒙uni 授权查询与openAppAuthorizeSetting
小程序getSetting/authorize/openSetting,别名映射scope.*
H5Permissions API /getUserMedia/ Notification;openSetting返回false
nvue不依赖document监听plusready,轮询plus就绪
uni-app xUTS 代码在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 / 小程序后台声明哪项。


用法

只读检测

constret=awaitcheck('camera')if(ret.status===PermissionStatus.GRANTED){// 已授权}elseif(ret.status===PermissionStatus.UNSUPPORTED){// 当前端无此项}else{// 未授权}

申请与永久拒绝

constret=awaitrequest('location')if(ret.status===PermissionStatus.GRANTED){// 继续}elseif(ret.status===PermissionStatus.PERMANENTLY_DENIED){awaitopenSetting({permission:'location'})}

ensure

constret=awaitensure('microphone',{request:true,openSettingOnDenied:true})if(ret.status!==PermissionStatus.GRANTED){uni.showToast({title:'需要麦克风权限',icon:'none'})return}

定位:先系统开关,再 App 权限

系统定位开关与 App 定位权限是两层状态,分开检测:

asyncfunctiongetMyLocation(){letservice=awaitcheck('locationService')if(service.status===PermissionStatus.SERVICE_OFF){awaitopenSetting({type:SettingType.LOCATION_SERVICE})service=awaitcheck('locationService')if(service.status!==PermissionStatus.GRANTED){uni.showToast({title:'请开启系统定位',icon:'none'})return}}constappLoc=awaitensure('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)}})}

从设置页返回后复查

特殊权限打开设置后,授权结果在用户返回时才变化。在onShowcheck

exportdefault{data(){return{waiting:''}},asynconShow(){if(!this.waiting)returnconstret=awaitcheck(this.waiting)this.waiting=''// 按 ret.status 继续业务},methods:{asyncaskOverlay(){this.waiting='overlay'awaitopenSetting({permission:'overlay'})}}}

打开设置类型

awaitopenSetting({type:SettingType.APP})awaitopenSetting({type:SettingType.LOCATION_SERVICE})awaitopenSetting({type:SettingType.NOTIFICATION})awaitopenSetting({type:SettingType.OVERLAY})awaitopenSetting({type:SettingType.BATTERY})awaitopenSetting({type:SettingType.INSTALL})awaitopenSetting({type:SettingType.MANAGE_STORAGE})

权限别名(部分)

完整表见readme.md/permissions.js。业务使用别名;也支持直接传android.permission.XXXscope.xxxohos.permission.XXX

别名说明kindAndroidiOS / 小程序
location精确定位runtimeACCESS_FINE_LOCATIONWhenInUse /scope.userLocation
locationService系统定位开关service系统 Location系统开关 /locationEnabled
camera相机runtimeCAMERAcamera /scope.camera
microphone麦克风runtimeRECORD_AUDIOrecord /scope.record
photoRead读相册/媒体runtimeAPI 33+READ_MEDIA_*photoLibrary
notification通知runtimeAPI 33+POST_NOTIFICATIONS通知授权
overlay悬浮窗specialSYSTEM_ALERT_WINDOW仅 Android
bluetooth蓝牙runtimeAPI 31+BLUETOOTH_*bluetooth /scope.bluetooth
manageExternalStorage所有文件访问specialMANAGE_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

同类:writeSettingsbatteryOptimizationinstallPackagesmanageExternalStoragescheduleExactAlarmnotificationListener
request对这些别名打开设置页,不调用requestPermissions

iOS

读取uni.getAppAuthorizeSetting()字段(cameraAuthorizedlocationAuthorized等)。
字段不足时使用原生authorizationStatus
request触发对应request*Authorization;状态为permanentlyDenied时打开app-settings:

系统定位开关与 App 定位权限分离:locationServicelocation

小程序

uni.getSetting → authSetting[scope]uni.authorize({scope})uni.openSetting()// 永久拒绝后

别名locationscope.userLocationcamerascope.camera

nvue / plus 就绪

// 有 document:监听 plusready// 无 DOM:轮询 typeof plus !== 'undefined'

各端声明(部分)

Android(用到再加):

<uses-permissionandroid:name="android.permission.CAMERA"/><uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/><uses-permissionandroid:name="android.permission.RECORD_AUDIO"/><uses-permissionandroid:name="android.permission.POST_NOTIFICATIONS"/>

iOS Privacy Key(未填用途文案时系统拒绝授权):

用途Key
相机NSCameraUsageDescription
麦克风NSMicrophoneUsageDescription
相册NSPhotoLibraryUsageDescription
定位NSLocationWhenInUseUsageDescription

真机问题

  1. 永久拒绝后继续弹授权框无效
    状态为permanentlyDenied。调用openSetting打开设置页。

  2. App 定位已授权仍拿不到坐标
    系统定位总开关为serviceOff。先check('locationService')

  3. iOS 跳转定位设置无反应
    私有App-Prefs在高版本受限。回退app-settings:/uni.openAppAuthorizeSetting

  4. Android 13 读相册失败
    使用别名photoRead,插件按 API 选择READ_MEDIA_*

  5. 通知权限在 Android 12 申请无效果
    POST_NOTIFICATIONS从 API 33 起才是运行时权限。更低版本检测通知开关,打开通知设置页。

  6. 悬浮窗request后立刻check仍是 denied
    用户还在设置页。在onShow复查。

  7. 导入报 UTS 编译失败
    使用@/uni_modules/lf-permission/index.js。UTS 代码位于utssdk.pending

  8. nvue 中plus为 undefined
    未等待 plus 就绪。插件内部轮询;业务侧对权限 API 使用await

  9. 小程序authorize失败且无法再弹
    状态为permanentlyDenied,调用openSetting

  10. H5openSetting为 false
    浏览器无法打开站点权限页。用文案引导用户到地址栏站点设置。

  11. 声明了电话权限但业务未用
    应用市场上架被拒。无需求不要写入phone/sms等权限。

  12. manageExternalStorage上架失败
    所有文件访问属敏感特殊权限。无强需求不要使用。

  13. 后台定位申请失败
    Android 10+ 须先具备前台定位,再申请locationBackground

  14. 业务用数字码判断权限
    使用PermissionStatus字符串比较,例如ret.status === PermissionStatus.GRANTED


排查

console.log(getPlatform())console.log(awaitcheck('camera'))console.log(listPermissions({onlyCurrent:true}))

检查项:

  1. 当前端platform与预期是否一致
  2. ret.native是否为当前 API 应申请的权限串
  3. manifest / plist / 小程序是否已声明
  4. statusdenied还是permanentlyDenied/serviceOff
  5. 特殊权限是否在onShow复查
  6. 引入路径是否为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

接入要求:

  1. 插件目录为uni_modules/lf-permission
  2. @/uni_modules/lf-permission/index.js引入
  3. 按业务声明各端权限与 iOS 用途文案
  4. 定位场景区分locationServicelocation
  5. 特殊权限在设置返回后复查
  6. 敏感权限无需求不声明

调用示例:

awaitensure('camera',{request:true,openSettingOnDenied:true})

问题反馈

插件:https://ext.dcloud.net.cn/plugin?id=29096

邮箱:lingfugroup@gmail.com

返回列表