iB86/.qoder/quests/system-permission-check.md

370 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 系统权限检查功能设计文档
## 1. 概述
本设计文档旨在解决应用启动时出现"权限不足"提示的问题。通过分析代码发现,应用在启动时会检查必要的运行时权限,如果权限未被授予,会显示"权限申请失败"的吐司提示。本文档将详细分析权限检查机制,并提出改进方案。
## 2. 当前权限检查机制分析
### 2.1 应用权限需求
根据AndroidManifest.xml文件应用需要以下权限
```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 权限检查流程
当前权限检查流程如下:
```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<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 权限请求结果处理优化
```java
@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中添加:
```xml
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
```
并在代码中处理:
```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. 建立完善的异常处理机制