在鸿蒙 ASCF 开发权限相关功能时,常见现象是:用户拒绝授权后,再调用 has.authorize 不再弹窗,而是直接进入 fail。此时不能继续死等系统弹窗,需要使用设置类 API 把用户引导到元服务设置页,并在返回后重新确认权限状态。ASCF 里主要涉及两个方法:has.openSetting 用来打开元服务设置页,has.getSetting 用来查询已授权的权限列表。
一、openSetting 与 getSetting 的分工
has.openSetting 调用后拉起元服务设置页面,用户可在其中手动开启或关闭权限。调用示例:
- has.openSetting({
- success: (res) => {
- console.info('设置页返回:', res);
- // res 是用户在设置页操作后的权限状态
- },
- fail: (err) => {
- console.error('打开设置页失败:', err);
- }
- });
复制代码
has.getSetting 查询当前已授权的权限列表:
- has.getSetting({
- success: (res) => {
- console.info('已授权权限:', res);
- if (res['scope.userLocation']) {
- console.info('定位权限已开启');
- }
- if (res['scope.camera']) {
- console.info('相机权限已开启');
- }
- },
- fail: (err) => {
- console.error('查询失败:', err);
- }
- });
复制代码
返回值是一个对象,key 是 scope 名称,value 是 boolean。只会返回明确授权或拒绝的权限,没申请过的权限不会出现在结果里。两个方法的 success 返回值格式一样,都是 { 'scope.xxx': true/false } 对象。
设置页中显示给用户的权限名称,和代码里的 scope 不一定一样。例如设置页显示“位置信息”“相机”,而代码里是 scope.userLocation、scope.camera。弹窗引导时不要对用户说“请开启 scope.camera”,应说“请开启相机权限”。
二、最容易踩的坑:openSetting 必须先 authorize
openSetting 必须在 has.authorize 之后调用。如果项目从未调用过 authorize,直接调用 openSetting 会报错:
- {"errMsg":"operateWXData:fail no authorize"}
复制代码
也就是说,至少先弹过一次授权弹窗,不管用户同意还是拒绝,之后才能打开设置页。正确顺序是:先 authorize(成功或失败都算),再 openSetting。
三、设置页返回后状态会重置
用户在设置页操作后,之前 authorize 的授权状态会被重置。从设置页返回后,需要重新调用 authorize 或 getSetting 获取最新状态。
同时,openSetting 的 success 回调里的权限状态有些情况下可能不是最新的。保险起见,从设置页返回后再调一次 getSetting 确认。
另外,openSetting 不能代替 authorize。它只是引导用户手动操作,用户可以在设置页选择开启或关闭,开发者无法控制。
getSetting 的另一个限制是:它只返回之前 authorize 过的 scope。从来没申请过的权限不会出现在结果里。要检查某个权限是否已授权,得先 authorize 过一次。
四、典型使用场景
场景一:授权失败后弹窗引导去设置页。这是最常见流程:authorize 失败后调用 has.showModal,用户点“去设置”再调用 has.openSetting。
- Page({
- getLocationWithGuide() {
- has.authorize({
- scope: 'scope.userLocation',
- success: () => {
- has.getLocation({
- success: (res) => {
- console.info('位置:', res);
- }
- });
- },
- fail: (err) => {
- console.info('授权失败,引导去设置:', err);
- has.showModal({
- title: '需要定位权限',
- content: '请在设置中开启定位权限,否则无法使用此功能',
- confirmText: '去设置',
- cancelText: '取消',
- success: (modalRes) => {
- if (modalRes.confirm) {
- has.openSetting({
- success: (settingRes) => {
- console.info('用户从设置页返回');
- }
- });
- }
- }
- });
- }
- });
- }
- });
复制代码
场景二:先用 getSetting 检查,未授权再申请。
- Page({
- checkAndAuthorize() {
- has.getSetting({
- success: (res) => {
- if (res['scope.camera']) {
- this.openCamera();
- } else {
- has.authorize({
- scope: 'scope.camera',
- success: () => {
- this.openCamera();
- },
- fail: () => {
- has.showToast({ title: '需要相机权限' });
- }
- });
- }
- }
- });
- },
- openCamera() {
- const ctx = has.createCameraContext();
- // ...
- }
- });
复制代码
场景三:页面加载时检查所有权限状态,适合设置页展示开关。
- Page({
- data: {
- permissions: {
- location: false,
- camera: false,
- microphone: false,
- contact: false
- }
- },
- onShow() {
- this.checkPermissions();
- },
- checkPermissions() {
- has.getSetting({
- success: (res) => {
- this.setData({
- permissions: {
- location: !!res['scope.userLocation'],
- camera: !!res['scope.camera'],
- microphone: !!res['scope.record'],
- contact: !!res['scope.contact']
- }
- });
- }
- });
- },
- goToSetting() {
- has.openSetting();
- }
- });
复制代码
场景四:结合辅助功能权限。项目若用 has.openAccessibility 做辅助功能检测,用户拒绝后可引导去设置。err.code === 203 表示用户手动关闭了辅助功能。
- has.openAccessibility({
- success: () => {
- console.info('辅助功能已开启');
- },
- fail: (err) => {
- if (err.code === 203) {
- has.showModal({
- title: '需要辅助功能',
- content: '请在设置中开启辅助功能',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) {
- has.openSetting();
- }
- }
- });
- }
- }
- });
复制代码
五、完整流程与封装
authorize + openSetting 的流程可以概括为:
- 调用 has.authorize
- ├── success → 权限已拿到,继续业务
- └── fail
- ├── 弹窗提示用户
- │ ├── 用户点“去设置”
- │ │ └── 调用 has.openSetting
- │ │ └── 用户在设置页操作
- │ │ └── 返回后重新 authorize/getSetting
- │ └── 用户点“取消”
- │ └── 结束或降级处理
- └── getSetting 检查是否还有其他权限需要申请
复制代码
由于流程固定,可以封装 ensurePermission:
- function ensurePermission(scope, scopeName) {
- return new Promise((resolve, reject) => {
- has.authorize({
- scope: scope,
- success: () => resolve(),
- fail: () => {
- has.showModal({
- title: '需要' + scopeName + '权限',
- content: '请在设置中开启' + scopeName + '权限',
- confirmText: '去设置',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- has.openSetting({
- success: () => reject(new Error('需要用户重新授权'))
- });
- } else {
- reject(new Error('用户拒绝授权'));
- }
- }
- });
- }
- });
- });
- }
- async function getLocation() {
- try {
- await ensurePermission('scope.userLocation', '定位');
- const res = await new Promise((resolve, reject) => {
- has.getLocation({ success: resolve, fail: reject });
- });
- return res;
- } catch (err) {
- console.error(err.message);
- }
- }
复制代码
六、调试与落地提醒
在 DevEco Studio 的模拟器上,openSetting 可能表现和真机不一样,建议真机测试。项目里的 openSetting Demo 已经注册到“开放能力”分类下,可以直接体验;getSetting 的示例没有单独做,但文中代码可直接复制使用。
最后记住几个关键点:openSetting 必须在 authorize 之后;设置页返回后授权状态会重置,要重新确认;getSetting 只返回申请过的权限;openSetting 回调状态可能不是最新,必要时用 getSetting 二次确认。 |