当前位置: 首页 > article >正文

解决 uniapp 开发中的相机相册权限申请同步告知目的问题(兼容 Android 13)| 华为应用商店上架解决方案

本文将分享如何在 uniapp 开发中申请相机和相册权限时,解决应用商店要求同步告知权限申请目的的问题,并兼容 Android 13。

背景

在某一次版本迭代中,上架华为应用商店审核不通过,原因是申请相机、相册权限时「未同步告知权限申请的使用目的」。

审核方给出的修改建议如下:

修改建议:APP在申请敏感权限时,应同步说明权限申请的使用目的,包括但不限于申请权限的名称、服务的具体功能、用途;告知方式不限于弹窗、蒙层、浮窗、或者自定义操作系统权限弹框等。请排查应用内所有权限申请行为,确保均符合要求。

特别提示:需在申请权限的同时/过程中同步告知目的。

在申请权限的同时给用户同步告知目的,建议申请目的与“权限弹窗”需同屏展示。

分析

简而言之,申请权限之前,需要同步显示“提示”,告知用户权限的使用目的,不能提前告知,也不能在申请权限之后再告知。

  • 因为不能每次都显示申请目的,如何判断当前是否已经拥有权限?
  • 在用户未对权限弹窗进行操作时,如何确保告知目的不会消失,并且与权限弹窗同屏显示?

难点:

  1. 判断权限是否已授权
  2. 权限申请流程
  3. 实现持久性弹窗

解决

由于主要针对安卓应用商店,这里只考虑安卓的情况,iOS部分较为简单。

回到难点上,

如何判断当前有无权限

在 uniapp 中可以使用 plus.navigator.checkPermission(permission) API 检查运行环境的权限,入参 permission 是权限的名称。其中,相机、相册的权限名称分别为:

  • android.permission.CAMERA
  • android.permission.READ_EXTERNAL_STORAGE

该 API 返回一个字符串,表示权限的授权状态。authorized 代表已被用户授权使用此权限。其他返回值请查阅 此处。

因此,我们只需要判断返回的权限状态是否为 authorized,如果是,则表示已有权限,无需继续申请;否则,需要申请对应权限。

申请权限

使用 plus.android.requestPermissions(Array[String] permissions, AndroidSuccessCallback successCb, AndroidErrorCallback errorCB) API 可以向系统请求权限。其中,permissions 是申请的权限列表,successCb 是申请权限成功后的回调函数,errorCB 是失败回调函数(通常用于处理参数错误的情况)。

  • 成功回调函数 successCb 包含三个参数:
    • granted - 已获取的权限列表(字符串数组);
    • deniedPresent - 被临时拒绝的权限列表(字符串数组);
    • deniedAlways - 被永久拒绝的权限列表(字符串数组)。

官方使用示例:

// 申请定位权限
function requestLocation(){
	plus.android.requestPermissions(['android.permission.ACCESS_FINE_LOCATION'], function(e){
		if(e.deniedAlways.length>0){	//权限被永久拒绝
			// 弹出提示框解释为何需要定位权限,引导用户打开设置页面开启
			console.log('Always Denied!!! '+e.deniedAlways.toString());
		}
		if(e.deniedPresent.length>0){	//权限被临时拒绝
			// 弹出提示框解释为何需要定位权限,可再次调用plus.android.requestPermissions申请权限
			console.log('Present Denied!!! '+e.deniedPresent.toString());
		}
		if(e.granted.length>0){	//权限被允许
		    //调用依赖获取定位权限的代码
			console.log('Granted!!! '+e.granted.toString());
		}
	}, function(e){
	    console.log('Request Permissions error:'+JSON.stringify(e));
	});
}

官方示例中已经提到,如果权限被永久拒绝,需要提示用户手动打开系统设置进行授权。以下是实现代码:

// 引导用户授权
export function guideUserToAuthorize(modelContent = '请打开对应权限(点击确定后在权限中授权对应权限)') {
	uni.showModal({
		title: '提示',
		content: modelContent,
		success: (res) => {
			if (res.confirm) {
				openAppDetailedSettingsPage();
			}
		}
	});
}
// 打开应用详细设置页面
export function openAppDetailedSettingsPage() {
	var Intent = plus.android.importClass("android.content.Intent");
	var Settings = plus.android.importClass("android.provider.Settings");
	var Uri = plus.android.importClass("android.net.Uri");
	var mainActivity = plus.android.runtimeMainActivity();
	var intent = new Intent();
	intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
	var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
	intent.setData(uri);
	mainActivity.startActivity(intent);
}

对于相机和相册权限的申请,无论是永久拒绝还是临时拒绝,都需要引导用户打开设置页面进行授权。只有在权限被允许时,才能继续执行相关的业务操作。

持久性弹窗

在 uniapp 中,可以使用 uni-popup 组件来实现持久性弹窗。 该组件可以手动打开或关闭,并且可以自定义样式。

何时打开何时关闭?

如果检查权限时发现权限未被授予,则在申请权限之前打开弹窗;在用户操作后触发成功回调函数时,关闭弹窗。

<uni-popup ref="cameraPopup" type="top">
	<view class="popup-content">
		<view class="title">相机权限说明</view>
		<view class="content">你的目的</view>
	</view>
</uni-popup>
// 权限说明弹窗
	.popup-content {
		position: relative;
		z-index: 2000;
		background-color: #eee;
		width: 90%;
		margin: 0 auto;
		margin-top: 10vh;
		padding: 32rpx;
		border-radius: 32rpx;
		color: #000;
		// 阴影
		box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.3);
		.title {
			font-size: 32rpx;
			font-weight: bold;
		}
		.content {
			margin-top: 16rpx;
			line-height: 1.5;
		}
	}

涉及到业务代码,这里只给出部分关键代码。

参考代码1(未兼容Android13):

由于相机、相册一般是一起使用,这份代码同时申请了相机、相册的权限。

函数 guideUserToAuthorize 参考上面给出的代码。

<template>
	<view>
		<uni-popup ref="cameraPopup" type="top">
			<view class="popup-content">
				<view class="title">相机权限说明</view>
				<view class="content">相机目的(根据实际业务替换)</view>
			</view>
		</uni-popup>
		<uni-popup ref="galleryPopup" type="top">
			<view class="popup-content">
				<view class="title">相册权限说明</view>
				<view class="content">相册目的(实际业务替换)</view>
			</view>
		</uni-popup>
	</view>
</template>

<script>
import { guideUserToAuthorize } from '@/utils';
    
	export default {
		components: {},
		props: {
		},
		data() {
			return {
			};
		},
		methods: {
			$_chooseImage() {
				let platform = uni.getSystemInfoSync().platform; //首先判断app是安卓还是ios
				if (platform == "ios") { //这里是ios的方法
                    // 执行业务代码
				} else if (platform == "android") {
					if (plus.navigator.checkPermission('android.permission.CAMERA') === 'authorized' && plus.navigator.checkPermission('android.permission.READ_EXTERNAL_STORAGE') === 'authorized') {
						// 已经获得相机和相册权限
                          // 执行业务代码
					} else {
						// 其中一个权限未取得或被拒绝
						this.getAndroidCameraPermission()
					}
				}
			},
			// 获得安卓相机权限
			getAndroidCameraPermission() {
				this.$refs.cameraPopup.open();
				plus.android.requestPermissions(['android.permission.CAMERA'], (e) => {
					this.$refs.cameraPopup.close();
					if (e.deniedAlways.length > 0) { //权限被永久拒绝
						// 弹出提示框解释为何需要权限,引导用户打开设置页面开启  
						guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
					} else if (e.deniedPresent.length > 0) { //权限被临时拒绝  
						// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限  
						guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
					} else {
						this.$refs.galleryPopup.open();
						plus.android.requestPermissions(['android.permission.READ_EXTERNAL_STORAGE'], (e) => {
                              this.$refs.galleryPopup.close();
							if (e.deniedAlways.length > 0) { //权限被永久拒绝  
								// 弹出提示框解释为何需要权限,引导用户打开设置页面开启
								guideUserToAuthorize('请打开相册存储功能(点击确定后在权限中授权相册存储功能)')  
							} else if (e.deniedPresent.length > 0) { //权限被临时拒绝  
								// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限  
								guideUserToAuthorize('请打开相册存储功能(点击确定后在权限中授权相册存储功能)')
							} else {
                                   // 执行业务代码
							}
						})
					}
				})
			}
        }
	};
</script>

<style lang="scss">
	// 权限说明弹窗
	.popup-content {
		position: relative;
		z-index: 2000;
		background-color: #eee;
		width: 90%;
		margin: 0 auto;
		margin-top: 10vh;
		padding: 32rpx;
		border-radius: 32rpx;
		color: #000;
		// 阴影
		box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.3);
		.title {
			font-size: 32rpx;
			font-weight: bold;
		}
		.content {
			margin-top: 16rpx;
			line-height: 1.5;
		}
	}
</style>

Android 13

问题来了,Android 13 的相册权限变更为 READ_MEDIA_IMAGES,如果继续使用之前的代码,将无法正确判断相册权限。因此,在判断和申请相册权限时,还需要判断当前系统版本。

plus.os.version 可以获取系统版本信息。

参考代码2(兼容Android13):

<template>
	<view>
		<uni-popup ref="cameraPopup" type="top">
			<view class="popup-content">
				<view class="title">相机权限说明</view>
				<view class="content">相机目的(根据实际业务替换)</view>
			</view>
		</uni-popup>
		<uni-popup ref="galleryPopup" type="top">
			<view class="popup-content">
				<view class="title">相册权限说明</view>
				<view class="content">相册目的(实际业务替换)</view>
			</view>
		</uni-popup>
	</view>
</template>

<script>
import { guideUserToAuthorize } from '@/utils';

	export default {
		data() {
			return {
			};
		},
		methods: {
			$_chooseImage() {
				let platform = uni.getSystemInfoSync().platform; //首先判断app是安卓还是ios
				if (platform == "ios") { //这里是ios的方法
					// 执行业务代码
				} else if (platform == "android") {
					const sdkVersion = parseInt(plus.os.version.split('.')[0], 10);
					const cameraPermissionState = plus.navigator.checkPermission('android.permission.CAMERA')
					const readExternalStoragePermissionState = plus.navigator.checkPermission('android.permission.READ_EXTERNAL_STORAGE')
					const readMediaImagesPermissionState = plus.navigator.checkPermission('android.permission.READ_MEDIA_IMAGES')
					// Android 13 的照片权限是 READ_MEDIA_IMAGES
					if ((sdkVersion < 13 && cameraPermissionState === 'authorized' && readExternalStoragePermissionState === 'authorized')
						|| (sdkVersion >= 13 && cameraPermissionState === 'authorized' && readMediaImagesPermissionState === 'authorized')
					) {
						// 已经获得相机和相册权限
                        // 执行业务代码
					} else {
						// 其中一个权限未取得或被拒绝
						this.getAndroidCameraPermission()
					}
				}
			},
			// 获得安卓相机权限
			getAndroidCameraPermission() {
				this.$refs.cameraPopup.open();
				plus.android.requestPermissions(['android.permission.CAMERA'], (e) => {
					this.$refs.cameraPopup.close();
					if (e.deniedAlways.length > 0) { //权限被永久拒绝
						// 弹出提示框解释为何需要权限,引导用户打开设置页面开启  
						// this.guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
						guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
					} else if (e.deniedPresent.length > 0) { //权限被临时拒绝  
						// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限  
						// this.guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
						guideUserToAuthorize('请打开手机相机功能(点击确定后在权限中授权相机功能)')
					} else {
						this.$refs.galleryPopup.open();
						const sdkVersion = parseInt(plus.os.version.split('.')[0], 10);
						let permissionName = 'READ_EXTERNAL_STORAGE'
						// Android 13 的照片权限是 READ_MEDIA_IMAGES
						if (sdkVersion >= 13) {
							permissionName = 'READ_MEDIA_IMAGES'
						}
						plus.android.requestPermissions([`android.permission.${permissionName}`], (e) => {
							this.$refs.galleryPopup.close();
							if (e.deniedAlways.length > 0) { //权限被永久拒绝  
								// 弹出提示框解释为何需要权限,引导用户打开设置页面开启
								guideUserToAuthorize('请打开相册存储功能(点击确定后在权限中授权相册存储功能)')
							} else if (e.deniedPresent.length > 0) { //权限被临时拒绝  
								// 弹出提示框解释为何需要权限,可再次调用plus.android.requestPermissions申请权限  
								guideUserToAuthorize('请打开相册存储功能(点击确定后在权限中授权相册存储功能)')
							} else {
								// 执行业务代码
							}
						})
					}
				})
			},
		}
	};
</script>

<style lang="scss">
	// 权限说明弹窗
	.popup-content {
		position: relative;
		z-index: 2000;
		background-color: #eee;
		width: 90%;
		margin: 0 auto;
		margin-top: 10vh;
		padding: 32rpx;
		border-radius: 32rpx;
		color: #000;
		// 阴影
		box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.3);
		.title {
			font-size: 32rpx;
			font-weight: bold;
		}
		.content {
			margin-top: 16rpx;
			line-height: 1.5;
		}
	}
</style>

总结

通过使用 checkPermission API 判断权限状态后,再决定是否调用 requestPermissions API 申请权限。同时,使用uni-popup 弹窗同步告知目的,申请权限前显示弹窗,用户操作后关闭。

参考

  • 查询权限 API checkPermission 介绍
  • 请求权限 API requestPermissions 介绍
  • 系统版本属性 plus.os.version 介绍

首发地址:https://blog.xchive.top/2025/solve-the-problem-of-synchronized-notification-purpose-of-camera-album-permission-request-in-uniapp-development.html


http://www.kler.cn/a/472530.html

相关文章:

  • 在 PhpStorm 中配置命令行直接运行 PHP 的步骤
  • Spring项目创建流程及配置文件bean标签参数简介
  • nginx 日志规范化意义及实现!
  • 《Opencv》信用卡信息识别项目
  • 【工业场景】用YOLOv8实现工业安全帽识别
  • 分享3个国内使用正版GPT的网站【亲测有效!2025最新】
  • 【C语言】_冒泡排序及其优化思路
  • 用Python实现货运分析地图应用
  • 经典多模态模型CLIP - 直观且详尽的解释
  • onLoad 生命周期函数是否执行取决于跳转的方式和小程序的页面栈管理机制
  • 移动支付安全:五大威胁及防护策略
  • spark functions函数合集(无示例)
  • dockerfile 中 #(nop)
  • 物联网协议:比较MQTT、CoAP和HTTP以实现高效设备通信
  • 【Leetcode 热题 100】33. 搜索旋转排序数组
  • vulnhub靶场-Deathnote(至获取shell)
  • Linux下文件重定向
  • 【OJ刷题】同向双指针问题
  • CSS语言的编程范式
  • 变压器的啸叫、气隙、中心抽头
  • 表格、列表和表单标签
  • JAVA开发学习Day8
  • hive在大数据体系里面起到什么作用
  • CSS 伪类和伪元素:为你的选择器注入更多活力
  • 开源免费GitHub搭建资源分享站
  • NGINX 支持 UDP 协议