权限合规
官方要求
Android 官方权限申请描述
Android 6.0 开始,对权限申请进行精细控制,每一个版本都有不同要求。其表象目前可以总结为:首次申请无限制,二次申请需理由,三次以后申请只能引导到权限设置页面,特殊权限需要跳转到特定系统界面设置。
当然因为 Android 厂商众多、定制 Rom 众多、系统版本众多,具体表现以具体手机为准。
海外权限申请审核:以 Android 官方权限申请描述为标准,暂无其他限制。
国内权限申请审核:增加 48 小时 限制,时限之内不允许申请(具体规定大家都在探索中)。
1.获取权限状态
接口说明
- 判断游戏是否拥有某个权限;
- 权限字符串 通过 android.Manifest.permission获取;
接入代码示例
- Java
OmniSDKGetPermissionOptions bluetoothOpts = OmniSDKGetPermissionOptions
.builder()
// 权限字符串
.permission(Manifest.permission.BLUETOOTH)
.build();
int bluetoothAdmin = OmniSDKv3.getInstance().getPermissionGrantedStatus(gameActivity, bluetoothOpts);
参数描述
OmniSDKGetPermissionOptions
参数 | 类型 | 说明 |
---|---|---|
permission | String | Android系统权限常量字符串,例子: Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE 读写权限 Manifest.permission.BLUETOOTH 蓝牙权限 Manifest.permission.SYSTEM_ALERT_WINDOW 系统悬浮窗权限 |
返回值
返回值 | 描述 | 处理方式 | 备注 |
---|---|---|---|
-1 | 该权限生效高于当前系统版本 | 该权限在高于当前系统版本时才会生效。该权限根据系统规定:无此功能、直接授予、权限组授予(即另一个权限申请时自动授予)申请时无需额外处理,相关功能需要项目适配处理。 | |
0 | 应用已被授予该权限 | 无需再申请,直接使用相关功能 | |
1 | 未授予,且未真实申请过 | 弹出首次申请的弹窗: 1. 取消弹窗,则增加时间限制; 2. 同意弹窗,则真实申请,真实申请+1。 | 国内默认48小时,海外无 |
148 | 未授予,时间限制无法申请 | 处理逻辑同1 | 同1 |
2 | 未授予,且被拒绝过一次 | 弹出第二次申请的弹窗: 1. 取消弹窗,则增加时间限制; 2. 同意弹窗,则真实申请,真实申请+1。 | 同1 部分权限可以无限制申请,情况以厂商系统为准。 |
248 | 未授予,时间限制无法申请 | 处理逻辑同2 | 同1 |
3 | 未授予,且被永久拒绝 | 弹出引导到系统界面设置的弹窗: 1. 取消弹窗,则增加时间限制; 2. 同意弹窗,则跳转到系统的应用权限管理界面,用户手动开启权限。 | 部分权限可以无限制申请,情况以厂商系统为准。 |
348 | 未授予,时间限制无法申请 | 处理逻辑同3 | 同1 |
2.申请权限
- 游戏特定功能 申请对应权限
- 权限字符串 通过 android.Manifest.permission获取;
接入代码示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
// 构建 引导权限请求弹窗信息
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要访问手机的存储空间,用于游戏资源更新等\\n\\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
// 构建 跳转应用设置开启权限弹窗
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要访问手机的存储空间,用于游戏资源更新等\\n\\n请在系统设置界面-应用权限,开启访问存储空间的权限")
.ok("去开启")
.cancel("取消")
.build();
// 构建申请权限对应参数
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.timeLimit(0)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
Log.i(tag, "onExternalStorage: " + result.get());
} else {
OmniSDKError error = result.error();
Log.e(tag, "onExternalStorage error:" + error);
}
});
参数描述
OmniSDKRequestPermissionsOptions
参数 | 类型 | 说明 |
---|---|---|
permissionsList | List< String > | 需要请求具体权限字符串List集合 |
showApplyDialog | Boolean | 是否使用 OmniSDK 内部弹窗,特殊权限必弹 |
timeLimit | Int | 请求申请权限的时间间隔 单位 小时 |
applyDialog | OmniSDKPermissionRationaleOptions | 引导用户申请权限的弹窗文案 |
guideToSettingDialog | OmniSDKPermissionRationaleOptions | 引导用户去设置界面开启权限的弹窗文案 |
OmniSDKPermissionRationaleOptions
参数 | 类型 | 说明 |
---|---|---|
title | String | 申请权限弹窗标题 |
rationale | String | 申请权限理由 |
ok | String | 申请权限同意Button的文案 |
cancel | String | 申请权限取消Button的文案 |
返回值
OmniSDKRequestPermissionsResult
参数 | 类型 | 说明 |
---|---|---|
allGranted | Boolean | 是否同意权限 |
deniedList | List< String > | 拒绝权限的具体信息 |
grantedList | List< String > | 同意权限的具体信息 |
3.常用权限示例
1.存储权限-storage(建议废弃,使用应用私有空间存储)
- 申请方案示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要访问手机的存储空间,用于游戏资源更新等\\n\\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要访问手机的存储空间,用于游戏资源更新等\\n\\n请在系统设置界面-应用权限,开启访问存储空间的权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.timeLimit(0)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
Log.i(tag, "onExternalStorage: " + result.get());
} else {
OmniSDKError error = result.error();
Log.e(tag, "onExternalStorage error:" + error);
}
});
存储权限变更历史
Android 10 开始系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过
MediaStore API
来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。即原来通过
READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
权限获取存储空间所有文件类型读写的权限开始收紧。存储空间访问权限开始分为三个范围:应用私有、其他应用私有、共享空间。
应用私有 在 Target(19) 开始不需要申请权限即可完全访问读写,其唯一“缺点”就是随应用卸载而删除数据。
注意:根据我们对大部分游戏的分析,游戏的资源更新、图片分享等存储需求,完全可以只使用应用私有 空间,不需要额外申请权限,建议游戏直接适配此方法。
使用
context.getExternalFilesDir(null)
方法获取其根路径再进行读写。根路径固定为:storage/Android/data/package-name/files/
示例:
File file = new File(context.getExternalFilesDir(null) + "/game/", "update.txt");
Android 10 开始我们的应用向媒体库添加的图片、音频或视频,将会自动拥有其读写权限,不需要额外申请
READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
权限。如果要读取其他应用程序向媒体库贡献的图片、音频或视频,则必须要申请
READ_EXTERNAL_STORAGE
权限才行。WRITE_EXTERNAL_STORAGE
权限将会在未来的 Android 版本中废弃。即非文件类应用不能访问共享存储空间和其他应用的私有存储空间。如果想完全访问需要申请MANAGE_EXTERNAL_STORAGE
。从 Android 13 开始,如果你的应用 Target 指定到了 33 或以上,那么
READ_EXTERNAL_STORAGE
权限就完全失去了作用,申请它将不会产生任何的效果。从 Android 13 开始,Android 将 图片、音频、视频 这三类文件的权限变更为单独权限来申请:
READ_MEDIA_IMAGES
管理手机的照片(权限组READ_MEDIA_VISUAL
)。READ_MEDIA_VIDEO
管理手机的视频(权限组READ_MEDIA_VISUAL
)。READ_MEDIA_AUDIO
管理手机的音频文件(权限组 READ_MEDIA_AURAL`)。且可能审核会要求为相应领域类型的应用才可申请相关权限。
以上,建议游戏使用 应用私有 空间,进行读写自己的文件同时无需申请权限,避免复杂的适配。
Target <= 32
- AndroidManifest.xml 声明
<application
android:requestLegacyExternalStorage="true"> <!-- 如果 Target>=29 还需要添加这行 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Target >= 33
- android.permission.READ_EXTERNAL_STORAGE 失效,暂时可以不用管,等渠道审核要求。
- 按国内厂商要求,一般二年左右会有要求,具体以厂商通知为准。
- 建议使用 应用私有 空间,无需权限申请。
2.访问电话状态权限(建议废弃)
android.permission.READ_PHONE_STATE
权限是允许访问电话状态权限,对imei
,deviceId
等 id 的获取,即设备唯一标识码。- 在 6.0 之前只需注册后就可以使用,Android 6.0 开始需要动态申请。
- Android10 开始,
READ_PHONE_STATE
被取消掉,申请无反应,使用相关 API,直接抛异常;换成系统权限READ_PRIVILEGED_PHONE_STATE
,此权限只能在系统 APP 中才可以被使用;普通应用无法申请。- 国内改用 OAID,海外改用 Google ADId,但是用户可以重置。
- AndroidManifest.xml 声明
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="28"/>
- 申请方案示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.READ_PHONE_STATE);
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要获取设备唯一标识码用于用户识别、统计\n\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要获取设备唯一标识码用于用户识别、统计\n\n请在系统设置界面-应用权限,开启访问唯一识别码的权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.timeLimit(48)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
DemoLogger.i(tag, "onPhoneStateImpl: " + result.get());
callback.onResult(
requestResult.getAllGranted(),
requestResult.getDeniedList(),
requestResult.getGrantedList()
);
} else {
OmniSDKError error = result.error();
Log.e(tag, "onPhoneStateImpl error:" + error);
}
});
3.位置权限
精确定位与模糊定位
- Android 12 起且 Target 31,Android 官方提供精确定位和模糊定位的判断。之前版本不支持,由厂商提供其对应的 API 判断。
- 因此,Target ≤ 30 且支持模糊定位的机型(小米 / Vivo),需使用厂商提供的 API 进行判断,OmniSDK 目前不提供适配。
- Target ≥ 31 时,直接使用系统 API 判断:
if (activity.getApplicationInfo().targetSdkVersion >= 31) {
boolean hasFineLocation = OmniSDKv3.getInstance().getPermissionGrantedStatus(activity, "android.permission.ACCESS_FINE_LOCATION") == 0;
}
后台定位
- Android 10 起且 Target 29,Android 引入后台定位权限
ACCESS_BACKGROUND_LOCATION
。 - Target = 29 时:应用需在
AndroidManifest
声明ACCESS_BACKGROUND_LOCATION
权限,然后动态申请该权限且用户选择 “始终允许” 才能获取到后台定位能力。 - Target ≥ 30 时:应用需在
AndroidManifest
声明ACCESS_BACKGROUND_LOCATION
权限,然后用户在系统设置页面上选择 “始终允许” 后才能获取到后台定位能力。 - 注意,
ACCESS_BACKGROUND_LOCATION
不可以单独申请,而是要应用程序在获得ACCESS_FINE_LOCATION
或ACCESS_COARSE_LOCATION
权限之后再申请,是一个特殊权限。 - OmniSDK 对此针对性适配,项目按要求声明、请求即可。
定位申请示例
请按隐私协议内容要求申请,三种类型,协议没用哪一种就不要声明或申请,否则审核不过。
- AndroidManifest.xml 声明
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
- 申请方案示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要获取模糊、精确、后台定位,用于推荐本地好友\n\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要获取模糊、精确、后台定位,用于推荐本地好友\n\n请在系统设置界面-应用权限,开启定位权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
DemoLogger.i(tag, "onLocationImpl: " + result.get());
callback.onResult(requestResult.getAllGranted(), requestResult.getDeniedList(), requestResult.getGrantedList());
} else {
OmniSDKError error = result.error();
Log.e(tag, "onLocationImpl error:" + error);
}
});
4.悬浮窗权限
特殊权限:Target >= 23 时,只能引导用户到设置界面开启。
- AndroidManifest.xml 声明
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 申请方案示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.SYSTEM_ALERT_WINDOW);
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要悬浮窗权限\n\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要悬浮窗权限\n\n请在系统设置界面,点击-允许显示悬浮窗,开启悬浮窗权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
DemoLogger.i(tag, "onFloatingWindowImpl: " + result.get());
callback.onResult(requestResult.getAllGranted(), requestResult.getDeniedList(), requestResult.getGrantedList());
} else {
OmniSDKError error = result.error();
Log.e(tag, "onFloatingWindowImpl error: " + error);
}
});
5.蓝牙权限
蓝牙功能的权限变更历史
- Target ≤ 28 时: 具备
BLUETOOTH
和BLUETOOT_ADMIN
权限就能使用连接类 API 和广播类 API,扫描类 API需要具备大致定位权限(ACCESS_COARSE_LOCATION
)。 - Target 为 29 和 30 时:连接类 API 和广播类 API 权限无变化,扫描类 API 需要另外具备精确定位权限(
ACCESS_FINE_LOCATION
)。 - Target ≥ 31 时: 新增细分的运行时蓝牙权限来替代
BLUETOOTH
和BLUETOOTH_ADIMIN
,为应用提供更灵活的权限申请方式。BLUETOOTH_SCAN
:允许扫描和发现设备,扫描类 API 需要同时具备该权限和精确定位权限(ACCESS_FINE_LOCATION
)。BLUETOOTH_CONNECT
:允许连接和访问已配对的设备,连接类 API 需要具备该权限。BLUETOOTH_ADVERTISE
:允许向附近的蓝牙设备进行广播,广播类 API 需要具备该权限。
- 不同 Target 版本 的不同蓝牙功能申请的权限对应关系表:
Target 版本 | 扫描类 | 连接类 | 广播 |
---|---|---|---|
<=28 | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.ACCESS_COARSE_LOCATION | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN |
29-30 | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission. ACCESS_FINE_LOCATION | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN | android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN |
>=31 | android.permission.BLUETOOTH_SCAN android.permission. ACCESS_FINE_LOCATION | android.permission.BLUETOOTH_CONNECT | android.permission.BLUETOOTH_ADVERTISE |
连接类功能申请蓝牙权限示例
Target < 31
- 符合隐私协议 & 在
AndroidManifest.xml
声明蓝牙权限 - 不需要申请
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
Target >= 31
- AndroidManifest.xml 声明
<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
- 申请方案示例
- Java
if (Build.VERSION.SDK_INT >= 31) {
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
// Requesting permissions is necessary for Android 12 and above to prevent Bluetooth API crashes.
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要蓝牙权限\n\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要蓝牙权限\\n\\n请在系统设置界面,开启蓝牙权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
DemoLogger.i(tag, "onBluetoothImpl: " + result.get());
callback.onResult(requestResult.getAllGranted(), requestResult.getDeniedList(), requestResult.getGrantedList());
} else {
OmniSDKError error = result.error();
Log.e(tag, "onBluetoothImpl error:" + error);
}
});
}
不同厂商蓝牙设置位置
- MIUI 13 :权限-连接附近的设备(
BLUETOOTH_CONNECT
)。
6.安装 APK 权限
- Target >= 26,Android 8 新增特殊权限,需要跳转特定界面申请。
- Android 11 特性调整,安装外部来源应用需要重启 App:https://cloud.tencent.com/developer/news/637591 ,即无法收到结果回调。
- Android 12 已经修复了此问题,授权或者取消授权后应用并不会重启应用。
- AndroidManifest.xml 声明
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
- 申请方案示例
- Java
List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.REQUEST_INSTALL_PACKAGES);
OmniSDKPermissionRationaleOptions applyDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要安装APk权限\n\n请允许权限申请")
.ok("允许")
.cancel("取消")
.build();
OmniSDKPermissionRationaleOptions guideDialog = OmniSDKPermissionRationaleOptions
.builder()
.title("温馨提示")
.rationale("(游戏)需要安装APk权限\n\n请在系统设置界面,开启-允许来自此来源的应用,打开安装APK权限")
.ok("去开启")
.cancel("取消")
.build();
OmniSDKRequestPermissionsOptions opts = OmniSDKRequestPermissionsOptions
.builder()
.showApplyDialog(true)
.permissionsList(permissions)
.applyDialog(applyDialog)
.guideToSettingDialog(guideDialog)
.build();
OmniSDKv3.getInstance().requestPermissions(appActivity, opts, result -> {
if (result.isSuccess()) {
OmniSDKRequestPermissionsResult requestResult = result.get();
DemoLogger.i(tag, "onApkInstallImpl: " + result.get());
callback.onResult(
requestResult.getAllGranted(),
requestResult.getDeniedList(),
requestResult.getGrantedList()
);
} else {
OmniSDKError error = result.error();
Log.e(tag, "onApkInstallImpl error:" + error);
}
});
渠道申请的权限列表
- 渠道申请的权限与权限列表
- 注:此文档收集为接入渠道里渠道文档或 sdk 有列明的。部分渠道会二次打包或分包,添加新东西,请以最终渠道审核包的
AndroidManifest.xml
权限列表为准。