13 KiB
13 KiB
系统权限检查功能设计文档
1. 概述
本设计文档旨在解决应用启动时出现"权限不足"提示的问题。通过分析代码发现,应用在启动时会检查必要的运行时权限,如果权限未被授予,会显示"权限申请失败"的吐司提示。本文档将详细分析权限检查机制,并提出改进方案。
2. 当前权限检查机制分析
2.1 应用权限需求
根据AndroidManifest.xml文件,应用需要以下权限:
<!-- 存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 音频权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
2.2 权限检查流程
当前权限检查流程如下:
graph TD
A[应用启动] --> B[检查存储权限]
B --> C{权限是否授予?}
C -->|否| D[请求存储权限]
C -->|是| E[检查录音权限]
E --> F{权限是否授予?}
F -->|否| G[请求录音权限]
F -->|是| H[检查位置权限]
H --> I{权限是否授予?}
I -->|否| J[请求位置权限]
I -->|是| K[检查相机权限]
K --> L{权限是否授予?}
L -->|否| M[请求相机权限]
L -->|是| N[初始化SDK]
2.3 权限检查代码分析
在MainActivity.java中,权限检查主要通过checkAndRequestPermissions()
方法实现:
private void checkAndRequestPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
return;
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
return;
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_CODE);
return;
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE);
}
}
权限请求结果处理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
boolean allGranted = true;
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
initializeSDK();
} else {
Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
}
}
}
3. 问题分析
3.1 当前实现存在的问题
-
逐个检查权限效率低:当前实现是逐个检查权限,如果第一个权限未授予,就立即请求该权限,而不会检查后续权限。
-
缺少权限分组处理:Android 6.0+引入了权限组概念,同一组的权限只需要用户授权一次。
-
用户体验不佳:用户需要多次授权不同的权限,而不是一次性授权所有需要的权限。
-
缺少权限说明:没有向用户解释为什么需要这些权限。
3.2 可能导致"权限不足"的原因
- 用户拒绝了某个或某些权限请求
- 在Android 11+设备上,某些权限需要特殊处理
- 权限请求时机不当
- 权限请求被系统或其他应用干扰
4. 改进方案
4.1 权限检查优化设计
graph TD
A[应用启动] --> B[检查所有必需权限]
B --> C{所有权限是否已授予?}
C -->|是| D[初始化SDK]
C -->|否| E[显示权限说明对话框]
E --> F[用户确认后请求所有未授权权限]
F --> G{用户是否全部授权?}
G -->|是| D
G -->|否| H[显示权限不足提示并引导用户手动授权]
4.2 权限分组策略
根据Android权限组,将权限分为以下几组:
权限组 | 包含权限 |
---|---|
存储权限组 | WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE |
麦克风权限组 | RECORD_AUDIO |
位置权限组 | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION |
相机权限组 | CAMERA |
蓝牙权限组 | BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT |
4.3 改进后的权限检查代码设计
private void checkAndRequestPermissions() {
List<String> permissionsNeeded = new ArrayList<>();
// 存储权限检查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
// 录音权限检查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.RECORD_AUDIO);
}
// 位置权限检查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
// 相机权限检查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.CAMERA);
}
if (permissionsNeeded.isEmpty()) {
// 所有权限已授予
initializeSDK();
} else {
// 显示权限说明对话框
showPermissionRationaleDialog(permissionsNeeded);
}
}
private void showPermissionRationaleDialog(List<String> permissions) {
new AlertDialog.Builder(this)
.setTitle("权限申请")
.setMessage("应用需要以下权限才能正常运行:\n\n" +
"• 存储权限: 用于保存数据和日志\n" +
"• 麦克风权限: 用于语音识别\n" +
"• 位置权限: 用于WiFi扫描\n" +
"• 相机权限: 用于视频功能\n\n" +
"请点击\"确定\"授予权限")
.setPositiveButton("确定", (dialog, which) -> {
requestPermissions(permissions.toArray(new String[0]), PERMISSION_REQUEST_CODE);
})
.setNegativeButton("取消", (dialog, which) -> {
Toast.makeText(this, "权限不足,应用可能无法正常运行", Toast.LENGTH_LONG).show();
})
.show();
}
4.4 权限请求结果处理优化
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
if (deniedPermissions.isEmpty()) {
// 所有权限已授予
initializeSDK();
} else {
// 有权限被拒绝
boolean shouldShowRationale = false;
for (String permission : deniedPermissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
shouldShowRationale = true;
break;
}
}
if (shouldShowRationale) {
// 用户拒绝了权限但没有选择"不再询问"
showPermissionDeniedDialog(deniedPermissions);
} else {
// 用户选择了"不再询问"或权限被永久拒绝
showPermissionPermanentlyDeniedDialog(deniedPermissions);
}
}
}
}
private void showPermissionDeniedDialog(List<String> deniedPermissions) {
new AlertDialog.Builder(this)
.setTitle("权限被拒绝")
.setMessage("以下权限被拒绝,应用可能无法正常运行:\n\n" +
formatDeniedPermissions(deniedPermissions) + "\n\n" +
"请重新授予权限")
.setPositiveButton("重新授权", (dialog, which) -> {
checkAndRequestPermissions();
})
.setNegativeButton("取消", (dialog, which) -> {
Toast.makeText(this, "权限不足,应用可能无法正常运行", Toast.LENGTH_LONG).show();
})
.show();
}
private void showPermissionPermanentlyDeniedDialog(List<String> deniedPermissions) {
new AlertDialog.Builder(this)
.setTitle("权限被永久拒绝")
.setMessage("以下权限被永久拒绝,请手动到设置页面授予权限:\n\n" +
formatDeniedPermissions(deniedPermissions) + "\n\n" +
"点击\"设置\"前往应用设置页面")
.setPositiveButton("设置", (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("取消", (dialog, which) -> {
Toast.makeText(this, "权限不足,应用可能无法正常运行", Toast.LENGTH_LONG).show();
})
.show();
}
5. 特殊权限处理
5.1 Android 11+ 存储权限处理
从Android 11开始,存储权限有所变化,需要在AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
并在代码中处理:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
// 请求所有文件访问权限
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
}
5.2 蓝牙权限处理
Android 12+对蓝牙权限进行了细分:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+需要BLUETOOTH_CONNECT和BLUETOOTH_ADVERTISE权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_ADVERTISE);
}
}
6. 测试策略
6.1 单元测试
- 权限检查方法测试
- 权限请求结果处理测试
- 不同Android版本兼容性测试
6.2 集成测试
- 应用首次启动权限请求流程测试
- 权限被拒绝后的处理流程测试
- 权限被永久拒绝后的处理流程测试
- 所有必需权限被授予后的正常启动测试
7. 实施计划
7.1 第一阶段:权限检查机制重构
- 实现批量权限检查
- 实现权限说明对话框
- 实现权限拒绝处理机制
7.2 第二阶段:特殊权限处理
- Android 11+存储权限处理
- Android 12+蓝牙权限处理
- 权限被永久拒绝的处理
7.3 第三阶段:用户体验优化
- 权限说明文案优化
- 权限请求时机优化
- 异常情况处理优化
8. 风险评估
8.1 技术风险
- 不同Android版本权限机制差异可能导致兼容性问题
- 权限请求时机不当可能影响用户体验
- 特殊设备可能对权限处理有特殊要求
8.2 解决方案
- 充分测试不同Android版本的权限处理
- 优化权限请求时机,在合适的场景下请求权限
- 建立完善的异常处理机制