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

13 KiB
Raw Blame History

系统权限检查功能设计文档

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 当前实现存在的问题

  1. 逐个检查权限效率低:当前实现是逐个检查权限,如果第一个权限未授予,就立即请求该权限,而不会检查后续权限。

  2. 缺少权限分组处理Android 6.0+引入了权限组概念,同一组的权限只需要用户授权一次。

  3. 用户体验不佳:用户需要多次授权不同的权限,而不是一次性授权所有需要的权限。

  4. 缺少权限说明:没有向用户解释为什么需要这些权限。

3.2 可能导致"权限不足"的原因

  1. 用户拒绝了某个或某些权限请求
  2. 在Android 11+设备上,某些权限需要特殊处理
  3. 权限请求时机不当
  4. 权限请求被系统或其他应用干扰

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 单元测试

  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. 建立完善的异常处理机制