1. 为什么你的UniApp应用会被应用商店拒绝?
最近不少UniApp开发者都遇到了一个头疼的问题:应用在vivo、小米等应用商店审核时被驳回,理由是"提前申请权限"。这个问题看似简单,但背后涉及到Android系统的权限机制变革。我去年就踩过这个坑,当时我们的电商应用因为一次性申请了相机、定位等8个权限,被小米商店连续驳回三次。
Android从6.0(API 23)开始引入了运行时权限机制,要求应用在真正需要使用功能时才向用户申请权限。但很多开发者(包括之前的我)还停留在老思维,习惯在应用启动时就申请所有可能用到的权限。这种"权限轰炸"的做法现在会被应用商店严格审查。
举个例子,如果你的社交应用在用户刚打开时就申请通讯录权限,但实际上这个权限只在"添加好友"功能中才会用到,这就是典型的违规行为。正确的做法应该是:当用户点击"同步通讯录好友"按钮时,再弹出权限申请对话框。
2. 理解Android的权限分类与规则
2.1 必要权限 vs 非必要权限
在解决这个问题前,我们需要先分清两种权限:
- 必要权限:没有它应用核心功能就无法运行。比如微信的相机权限(扫码登录必须)
- 非必要权限:只在特定场景下需要。比如电商App的定位权限(仅用于"附近门店"功能)
根据我的经验,90%的审核问题都出在非必要权限的申请时机上。去年我们团队的一个工具类App就因为在启动时申请了短信权限(实际只用在客服反馈功能中),被华为应用市场直接下架。
2.2 targetSdkVersion的关键作用
manifest.json中的这个参数决定了应用遵循哪个Android版本的规则:
{ "app-plus": { "distribute": { "android": { "targetSdkVersion": 26 } } } }必须设置为23或更高,否则应用商店会认为你的应用没有适配运行时权限机制。我建议直接设置为最新稳定版(目前是33),这样可以避免很多兼容性问题。
3. UniApp动态权限申请实战
3.1 修改manifest.json配置
首先要在manifest.json中关闭所有非必要权限的自动申请:
{ "permissions": { "permissionExternalStorage": { "request": "none" }, "permissionPhoneState": { "request": "none" } } }这个配置告诉UniApp:不要自动申请这些权限。等真正需要时,我们再通过代码动态申请。
3.2 按需申请权限的代码实现
UniApp提供了uni.authorize API来实现动态权限申请。以相机权限为例:
// 在扫码按钮的点击事件中 async function openScanner() { try { const status = await uni.authorize({ scope: 'scope.camera' }); // 用户已授权,执行扫码逻辑 scanQRCode(); } catch (err) { // 用户拒绝或系统拒绝 uni.showToast({ title: '需要相机权限才能扫码', icon: 'none' }); } }这种实现方式完全符合应用商店的要求:只有当用户点击扫码按钮时,才会触发相机权限申请。即使用户拒绝,也不会影响App其他功能的使用。
3.3 处理用户拒绝的情况
用户可能会拒绝权限申请,好的应用应该优雅地处理这种情况:
async function requestPermission(scope) { // 1. 检查是否已经授权 let setting = await uni.getSetting(); if (setting.authSetting[scope]) { return true; } // 2. 首次申请 try { await uni.authorize({ scope }); return true; } catch (err) { // 3. 被拒绝后引导用户手动开启 uni.showModal({ title: '权限申请', content: '需要授权才能使用该功能,是否去设置开启?', success(res) { if (res.confirm) { uni.openSetting(); } } }); return false; } }这套流程是我们团队经过多次测试总结出来的最佳实践,能显著降低用户流失率。
4. 常见权限场景与解决方案
4.1 定位权限的处理
很多应用滥用定位权限。正确的做法应该是:
// 只在进入"附近门店"页面时申请 onLoad() { this.requestLocationPermission(); } methods: { async requestLocationPermission() { const granted = await requestPermission('scope.userLocation'); if (granted) { this.getNearbyShops(); } } }4.2 存储权限的最佳实践
Android 11之后,存储权限分为两种:
- 媒体文件访问(图片/视频/音频)
- 其他文件访问
对于大多数应用,只需要访问媒体文件:
// 选择图片时申请权限 async function chooseImage() { try { await uni.authorize({ scope: 'scope.writePhotosAlbum' }); uni.chooseImage({ success() { // 处理图片 } }); } catch (err) { console.log('权限被拒绝'); } }4.3 通讯录权限的特殊处理
通讯录属于敏感权限,审核非常严格。必须确保:
- 只在相关功能触发时申请
- 提供清晰的用途说明
- 用户拒绝后不影响核心功能
// 同步通讯录好友时 async function syncContacts() { const granted = await requestPermission('scope.addressBook'); if (!granted) return; // 实际业务逻辑 }5. 测试与上架前的检查清单
在提交应用商店前,建议按照这个清单检查:
权限申请时机测试:
- 首次启动是否没有弹任何权限框?
- 每个权限是否都在对应功能首次使用时才申请?
拒绝场景测试:
- 拒绝权限后,应用是否不会崩溃?
- 是否还能使用其他功能?
manifest配置检查:
- targetSdkVersion ≥ 23
- 所有非必要权限都设置为"none"
隐私政策合规:
- 隐私政策中是否说明了每个权限的用途?
- 是否有权限使用情况的说明?
我们团队现在会在测试阶段专门安排1-2天做权限专项测试,使用不同品牌的Android手机反复验证各种权限场景。这个习惯让我们最近半年再没遇到过权限相关的审核问题。