# 系统权限检查功能设计文档
## 1. 概述
本设计文档旨在解决应用启动时出现"权限不足"提示的问题。通过分析代码发现,应用在启动时会检查必要的运行时权限,如果权限未被授予,会显示"权限申请失败"的吐司提示。本文档将详细分析权限检查机制,并提出改进方案。
## 2. 当前权限检查机制分析
### 2.1 应用权限需求
根据AndroidManifest.xml文件,应用需要以下权限:
```xml
```
### 2.2 权限检查流程
当前权限检查流程如下:
```mermaid
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()`方法实现:
```java
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);
}
}
```
权限请求结果处理:
```java
@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 当前实现存在的问题
1. **逐个检查权限效率低**:当前实现是逐个检查权限,如果第一个权限未授予,就立即请求该权限,而不会检查后续权限。
2. **缺少权限分组处理**:Android 6.0+引入了权限组概念,同一组的权限只需要用户授权一次。
3. **用户体验不佳**:用户需要多次授权不同的权限,而不是一次性授权所有需要的权限。
4. **缺少权限说明**:没有向用户解释为什么需要这些权限。
### 3.2 可能导致"权限不足"的原因
1. 用户拒绝了某个或某些权限请求
2. 在Android 11+设备上,某些权限需要特殊处理
3. 权限请求时机不当
4. 权限请求被系统或其他应用干扰
## 4. 改进方案
### 4.1 权限检查优化设计
```mermaid
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 改进后的权限检查代码设计
```java
private void checkAndRequestPermissions() {
List 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 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 权限请求结果处理优化
```java
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
List 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 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 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中添加:
```xml
```
并在代码中处理:
```java
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+对蓝牙权限进行了细分:
```java
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 单元测试
1. 权限检查方法测试
2. 权限请求结果处理测试
3. 不同Android版本兼容性测试
### 6.2 集成测试
1. 应用首次启动权限请求流程测试
2. 权限被拒绝后的处理流程测试
3. 权限被永久拒绝后的处理流程测试
4. 所有必需权限被授予后的正常启动测试
## 7. 实施计划
### 7.1 第一阶段:权限检查机制重构
- 实现批量权限检查
- 实现权限说明对话框
- 实现权限拒绝处理机制
### 7.2 第二阶段:特殊权限处理
- Android 11+存储权限处理
- Android 12+蓝牙权限处理
- 权限被永久拒绝的处理
### 7.3 第三阶段:用户体验优化
- 权限说明文案优化
- 权限请求时机优化
- 异常情况处理优化
## 8. 风险评估
### 8.1 技术风险
1. 不同Android版本权限机制差异可能导致兼容性问题
2. 权限请求时机不当可能影响用户体验
3. 特殊设备可能对权限处理有特殊要求
### 8.2 解决方案
1. 充分测试不同Android版本的权限处理
2. 优化权限请求时机,在合适的场景下请求权限
3. 建立完善的异常处理机制