跳到主要内容

权限合规

官方要求

  1. Android 官方权限申请描述

    Android 6.0 开始,对权限申请进行精细控制,每一个版本都有不同要求。其表象目前可以总结为:首次申请无限制,二次申请需理由,三次以后申请只能引导到权限设置页面,特殊权限需要跳转到特定系统界面设置

    当然因为 Android 厂商众多、定制 Rom 众多、系统版本众多,具体表现以具体手机为准。

  2. 海外权限申请审核:以 Android 官方权限申请描述为标准,暂无其他限制。

  3. 国内权限申请审核:增加 48 小时 限制,时限之内不允许申请(具体规定大家都在探索中)。

1.获取权限状态

接口说明

提示

接入代码示例

OmniSDKGetPermissionOptions bluetoothOpts = OmniSDKGetPermissionOptions
.builder()
// 权限字符串
.permission(Manifest.permission.BLUETOOTH)
.build();
int bluetoothAdmin = OmniSDKv3.getInstance().getPermissionGrantedStatus(gameActivity, bluetoothOpts);

参数描述

OmniSDKGetPermissionOptions

参数类型说明
permissionStringAndroid系统权限常量字符串,例子:
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未授予,时间限制无法申请处理逻辑同11
2未授予,且被拒绝过一次弹出第二次申请的弹窗: 1. 取消弹窗,则增加时间限制; 2. 同意弹窗,则真实申请,真实申请+1。1部分权限可以无限制申请,情况以厂商系统为准。
248未授予,时间限制无法申请处理逻辑同21
3未授予,且被永久拒绝弹出引导到系统界面设置的弹窗: 1. 取消弹窗,则增加时间限制; 2. 同意弹窗,则跳转到系统的应用权限管理界面,用户手动开启权限。部分权限可以无限制申请,情况以厂商系统为准。
348未授予,时间限制无法申请处理逻辑同31

2.申请权限

提示

接入代码示例

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

参数类型说明
permissionsListList< String >需要请求具体权限字符串List集合
showApplyDialogBoolean是否使用 OmniSDK 内部弹窗,特殊权限必弹
timeLimitInt请求申请权限的时间间隔 单位 小时
applyDialogOmniSDKPermissionRationaleOptions引导用户申请权限的弹窗文案
guideToSettingDialogOmniSDKPermissionRationaleOptions引导用户去设置界面开启权限的弹窗文案

OmniSDKPermissionRationaleOptions

参数类型说明
titleString申请权限弹窗标题
rationaleString申请权限理由
okString申请权限同意Button的文案
cancelString申请权限取消Button的文案

返回值

OmniSDKRequestPermissionsResult

参数类型说明
allGrantedBoolean是否同意权限
deniedListList< String >拒绝权限的具体信息
grantedListList< String >同意权限的具体信息

3.常用权限示例

1.存储权限-storage(建议废弃,使用应用私有空间存储)

  • 申请方案示例
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);
}
});

存储权限变更历史

  1. Android 10 开始系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过 MediaStore API 来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。

  2. 即原来通过 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限获取存储空间所有文件类型读写的权限开始收紧。

  3. 存储空间访问权限开始分为三个范围:应用私有、其他应用私有、共享空间。

    • 应用私有Target(19) 开始不需要申请权限即可完全访问读写,其唯一“缺点”就是随应用卸载而删除数据。

    • 注意:根据我们对大部分游戏的分析,游戏的资源更新、图片分享等存储需求,完全可以只使用应用私有 空间,不需要额外申请权限,建议游戏直接适配此方法

    • 使用 context.getExternalFilesDir(null) 方法获取其根路径再进行读写。根路径固定为: storage/Android/data/package-name/files/

    • 示例:File file = new File(context.getExternalFilesDir(null) + "/game/", "update.txt");

  4. Android 10 开始我们的应用向媒体库添加的图片、音频或视频,将会自动拥有其读写权限,不需要额外申请READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限。

  5. 如果要读取其他应用程序向媒体库贡献的图片、音频或视频,则必须要申请 READ_EXTERNAL_STORAGE 权限才行。

  6. WRITE_EXTERNAL_STORAGE 权限将会在未来的 Android 版本中废弃。即非文件类应用不能访问共享存储空间和其他应用的私有存储空间。如果想完全访问需要申请 MANAGE_EXTERNAL_STORAGE

  7. Android 13 开始,如果你的应用 Target 指定到了 33 或以上,那么 READ_EXTERNAL_STORAGE 权限就完全失去了作用,申请它将不会产生任何的效果。

  8. Android 13 开始,Android 将 图片、音频、视频 这三类文件的权限变更为单独权限来申请:

    • READ_MEDIA_IMAGES 管理手机的照片(权限组 READ_MEDIA_VISUAL)。

    • READ_MEDIA_VIDEO 管理手机的视频(权限组 READ_MEDIA_VISUAL)。

    • READ_MEDIA_AUDIO 管理手机的音频文件(权限组 READ_MEDIA_AURAL`)。

    • 且可能审核会要求为相应领域类型的应用才可申请相关权限。

  9. 以上,建议游戏使用 应用私有 空间,进行读写自己的文件同时无需申请权限,避免复杂的适配。

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 权限是允许访问电话状态权限,对 imeideviceId 等 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"/>
  • 申请方案示例
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_LOCATIONACCESS_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" />
  • 申请方案示例
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" />
  • 申请方案示例
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 时: 具备 BLUETOOTHBLUETOOT_ADMIN 权限就能使用连接类 API 和广播类 API,扫描类 API需要具备大致定位权限(ACCESS_COARSE_LOCATION)。
  • Target 为 29 和 30 时:连接类 API 和广播类 API 权限无变化,扫描类 API 需要另外具备精确定位权限(ACCESS_FINE_LOCATION)。
  • Target ≥ 31 时: 新增细分的运行时蓝牙权限来替代 BLUETOOTHBLUETOOTH_ADIMIN,为应用提供更灵活的权限申请方式。
    1. BLUETOOTH_SCAN:允许扫描和发现设备,扫描类 API 需要同时具备该权限和精确定位权限(ACCESS_FINE_LOCATION)。
    2. BLUETOOTH_CONNECT:允许连接和访问已配对的设备,连接类 API 需要具备该权限。
    3. BLUETOOTH_ADVERTISE:允许向附近的蓝牙设备进行广播,广播类 API 需要具备该权限。
  • 不同 Target 版本 的不同蓝牙功能申请的权限对应关系表
Target 版本扫描类连接类广播
<=28android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.ACCESS_COARSE_LOCATIONandroid.permission.BLUETOOTH android.permission.BLUETOOTH_ADMINandroid.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN
29-30android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission. ACCESS_FINE_LOCATIONandroid.permission.BLUETOOTH android.permission.BLUETOOTH_ADMINandroid.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN
>=31android.permission.BLUETOOTH_SCAN android.permission. ACCESS_FINE_LOCATIONandroid.permission.BLUETOOTH_CONNECTandroid.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" />
  • 申请方案示例
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 权限

提示
  1. Target >= 26,Android 8 新增特殊权限,需要跳转特定界面申请。
  2. Android 11 特性调整,安装外部来源应用需要重启 App:https://cloud.tencent.com/developer/news/637591 ,即无法收到结果回调。
  3. Android 12 已经修复了此问题,授权或者取消授权后应用并不会重启应用。
  • AndroidManifest.xml 声明
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
  • 申请方案示例
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);
}
});

渠道申请的权限列表

  1. 渠道申请的权限与权限列表
  2. 注:此文档收集为接入渠道里渠道文档或 sdk 有列明的。部分渠道会二次打包或分包,添加新东西,请以最终渠道审核包的 AndroidManifest.xml 权限列表为准。