Android权限请求代码重构:XXPermissions最佳实践

Android权限请求代码重构:XXPermissions最佳实践

【免费下载链接】XXPermissions Android 权限请求框架,已适配 Android 14 【免费下载链接】XXPermissions 项目地址: https://gitcode.com/GitHub_Trending/xx/XXPermissions

在Android应用开发中,权限请求是一个复杂且容易出错的环节。随着Android系统版本的不断升级,权限管理机制也在持续变化,从Android 6.0的运行时权限到Android 14的细粒度权限控制,开发者需要不断适配新的权限模型。传统的权限请求方式往往代码冗余、适配困难,且容易出现内存泄漏等问题。XXPermissions作为一款功能全面的Android权限请求框架,已适配Android 14,能够帮助开发者简化权限请求流程,提高代码质量和可维护性。本文将从重构痛点出发,详细介绍如何使用XXPermissions进行权限请求代码重构,并分享最佳实践。

重构前的痛点与挑战

在使用XXPermissions之前,传统的权限请求方式存在诸多问题,主要表现在以下几个方面:

代码冗余与可读性差

传统的权限请求需要在Activity或Fragment中编写大量重复代码,包括权限检查、请求权限、处理权限回调等。这些代码分散在各个组件中,导致代码结构混乱,可读性和可维护性降低。例如,在申请相机权限时,需要编写如下代码:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
            CAMERA_PERMISSION_REQUEST_CODE);
} else {
    // 权限已授予,执行相机操作
    openCamera();
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限授予成功,执行相机操作
            openCamera();
        } else {
            // 权限授予失败,提示用户
            Toast.makeText(this, "相机权限被拒绝,无法打开相机", Toast.LENGTH_SHORT).show();
        }
    }
}

这种方式不仅代码量大,而且当需要申请多个权限时,代码会变得更加臃肿。

版本适配复杂

不同Android版本的权限模型存在差异,例如Android 11引入了MANAGE_EXTERNAL_STORAGE权限,Android 12对通知权限进行了调整,Android 13细化了媒体权限等。传统的权限请求方式需要开发者手动判断系统版本,并根据不同版本适配不同的权限请求逻辑,这增加了开发难度和出错风险。

异常场景处理不足

在权限请求过程中,可能会遇到各种异常场景,如用户拒绝权限并勾选“不再询问”、Activity处于后台时申请权限导致崩溃等。传统的权限请求方式对这些异常场景的处理往往不够完善,容易导致应用出现闪退、功能异常等问题。

内存泄漏风险

在Android 12及以上版本中,如果在权限请求过程中调用了Activity.shouldShowRequestPermissionRationale方法后立即调用activity.finish(),可能会导致系统内存泄漏。传统的权限请求框架往往没有针对这些系统级问题进行优化,增加了应用的稳定性风险。

XXPermissions框架介绍

XXPermissions是一款功能强大的Android权限请求框架,具有简洁易用、支持全面、适配极端情况等特点。该框架采用链式调用的方式,简化了权限请求流程,并内置了丰富的功能来解决传统权限请求方式存在的问题。

框架特点

XXPermissions框架的主要特点如下:

  • 简洁易用:采用链式调用的方式,一行代码即可完成权限请求,大大减少了模板代码。
  • 支持全面:已适配Android 14,支持所有危险权限和特殊权限的申请,包括读取应用列表、闹钟提醒、画中画等特殊权限。
  • 自动版本适配:框架内部会根据系统版本自动适配不同的权限请求逻辑,例如在低版本设备上申请高版本权限时,会自动替换为对应的低版本权限。
  • 异常场景处理:内置了对屏幕旋转、后台申请权限、内存泄漏等异常场景的处理机制,提高了应用的稳定性。
  • 错误检测机制:在Debug模式下,框架会对权限申请过程中的不规范操作进行检测,并抛出异常提示开发者,帮助开发者尽早发现问题。

核心功能模块

XXPermissions框架的核心功能模块包括权限定义、权限申请、权限回调、权限拦截器等,各模块的关系如图所示:

XXPermissions框架核心功能模块

  • 权限定义:框架将权限封装为IPermission接口的实现类,如DangerousPermission、SpecialPermission等,方便开发者统一管理和申请权限。相关代码位于library/src/main/java/com/hjq/permissions/permission目录下。
  • 权限申请:通过XXPermissions类的静态方法with()创建权限请求实例,然后调用permission()方法添加需要申请的权限,最后调用request()方法发起请求。核心代码位于XXPermissions.java
  • 权限回调:通过OnPermissionCallback接口回调权限申请结果,开发者可以在回调中处理权限授予成功或失败的逻辑。相关代码位于OnPermissionCallback.java
  • 权限拦截器:通过OnPermissionInterceptor接口可以在权限申请前后插入自定义逻辑,如显示权限申请说明对话框等。相关代码位于OnPermissionInterceptor.java

XXPermissions集成与基础使用

集成步骤

要在项目中集成XXPermissions框架,需要按照以下步骤进行操作:

  1. 添加远程仓库

    如果项目的Gradle配置在7.0以下,需要在项目根目录的build.gradle文件中添加JitPack仓库:

    allprojects {
        repositories {
            // JitPack 远程仓库
            maven { url 'https://jitpack.io' }
        }
    }
    

    如果Gradle配置在7.0及以上,则需要在settings.gradle文件中添加:

    dependencyResolutionManagement {
        repositories {
            // JitPack 远程仓库
            maven { url 'https://jitpack.io' }
        }
    }
    
  2. 添加依赖

    在app模块的build.gradle文件中添加XXPermissions的依赖:

    dependencies {
        // 权限请求框架:https://gitcode.com/GitHub_Trending/xx/XXPermissions
        implementation 'com.github.getActivity:XXPermissions:26.5'
    }
    
  3. 支持AndroidX

    如果项目基于AndroidX库,需要在gradle.properties文件中添加以下配置:

    # 表示使用 AndroidX
    android.useAndroidX = true
    # 表示将第三方库迁移到 AndroidX
    android.enableJetifier = true
    

基础使用示例

XXPermissions框架的使用非常简单,通过链式调用即可完成权限请求。以下是申请相机和录音权限的示例代码:

XXPermissions.with(this)
        // 申请相机权限
        .permission(PermissionLists.getCameraPermission())
        // 申请录音权限
        .permission(PermissionLists.getRecordAudioPermission())
        // 设置权限申请回调
        .request(new OnPermissionCallback() {
            @Override
            public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                if (deniedList.isEmpty()) {
                    // 所有权限都已授予
                    Toast.makeText(MainActivity.this, "所有权限都已授予", Toast.LENGTH_SHORT).show();
                    // 执行需要权限的操作
                    openCameraAndRecordAudio();
                } else {
                    // 有权限被拒绝
                    Toast.makeText(MainActivity.this, "有权限被拒绝", Toast.LENGTH_SHORT).show();
                    // 判断是否有被永久拒绝的权限
                    if (XXPermissions.isDoNotAskAgainPermissions(MainActivity.this, deniedList)) {
                        // 有被永久拒绝的权限,跳转到权限设置页面
                        XXPermissions.startPermissionActivity(MainActivity.this, deniedList);
                    }
                }
            }
        });

上述代码中,XXPermissions.with(this)创建了一个权限请求实例,permission()方法用于添加需要申请的权限,request()方法发起权限请求并设置回调。在回调中,可以根据授予的权限列表和被拒绝的权限列表进行相应的处理。

代码重构最佳实践

单一权限申请重构

在传统的权限请求方式中,申请单一权限需要编写大量模板代码。使用XXPermissions框架可以将这些代码简化,提高可读性和可维护性。

重构前代码
private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;

private void requestCameraPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
            // 显示权限申请说明对话框
            new AlertDialog.Builder(this)
                    .setTitle("权限申请")
                    .setMessage("需要相机权限来拍摄照片")
                    .setPositiveButton("确定", (dialog, which) -> {
                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                                CAMERA_PERMISSION_REQUEST_CODE);
                    })
                    .setNegativeButton("取消", null)
                    .show();
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                    CAMERA_PERMISSION_REQUEST_CODE);
        }
    } else {
        openCamera();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            openCamera();
        } else {
            if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
                // 用户拒绝权限并勾选“不再询问”,跳转到权限设置页面
                new AlertDialog.Builder(this)
                        .setTitle("权限被拒绝")
                        .setMessage("相机权限被拒绝,无法拍摄照片,请前往设置中开启权限")
                        .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("取消", null)
                        .show();
            } else {
                Toast.makeText(this, "相机权限被拒绝", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
重构后代码

使用XXPermissions框架后,上述代码可以重构为:

private void requestCameraPermission() {
    XXPermissions.with(this)
            .permission(PermissionLists.getCameraPermission())
            .description(new PermissionDescription() {
                @Override
                public void askWhetherRequestPermission(@NonNull Activity activity, @NonNull List<IPermission> requestList, @NonNull Runnable continueRequestRunnable, @NonNull Runnable breakRequestRunnable) {
                    // 显示权限申请说明对话框
                    new AlertDialog.Builder(activity)
                            .setTitle("权限申请")
                            .setMessage("需要相机权限来拍摄照片")
                            .setPositiveButton("确定", (dialog, which) -> continueRequestRunnable.run())
                            .setNegativeButton("取消", (dialog, which) -> breakRequestRunnable.run())
                            .show();
                }
            })
            .request(new OnPermissionCallback() {
                @Override
                public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                    if (deniedList.isEmpty()) {
                        openCamera();
                    } else {
                        if (XXPermissions.isDoNotAskAgainPermissions(MainActivity.this, deniedList)) {
                            // 用户拒绝权限并勾选“不再询问”,跳转到权限设置页面
                            new AlertDialog.Builder(MainActivity.this)
                                    .setTitle("权限被拒绝")
                                    .setMessage("相机权限被拒绝,无法拍摄照片,请前往设置中开启权限")
                                    .setPositiveButton("去设置", (dialog, which) -> {
                                        XXPermissions.startPermissionActivity(MainActivity.this, deniedList);
                                    })
                                    .setNegativeButton("取消", null)
                                    .show();
                        } else {
                            Toast.makeText(MainActivity.this, "相机权限被拒绝", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            });
}
重构说明

重构后的代码通过XXPermissions的链式调用,将权限检查、申请、回调处理等逻辑整合在一起,减少了模板代码。同时,通过description()方法设置权限申请说明对话框,将权限申请前的说明逻辑与权限请求逻辑解耦,提高了代码的可读性和可维护性。此外,框架内置了isDoNotAskAgainPermissions()方法来判断用户是否勾选了“不再询问”,简化了异常场景的处理。

多权限申请重构

当需要申请多个权限时,XXPermissions框架的优势更加明显。传统的多权限申请需要编写大量的权限检查和请求代码,而使用XXPermissions可以轻松实现。

重构前代码
private static final int MULTIPLE_PERMISSIONS_REQUEST_CODE = 101;
private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};

private void requestMultiplePermissions() {
    List<String> deniedPermissions = new ArrayList<>();
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            deniedPermissions.add(permission);
        }
    }
    if (deniedPermissions.isEmpty()) {
        // 所有权限都已授予
        startRecording();
    } else {
        boolean shouldShowRationale = false;
        for (String permission : deniedPermissions) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                shouldShowRationale = true;
                break;
            }
        }
        if (shouldShowRationale) {
            // 显示权限申请说明对话框
            new AlertDialog.Builder(this)
                    .setTitle("权限申请")
                    .setMessage("需要相机和录音权限来录制视频")
                    .setPositiveButton("确定", (dialog, which) -> {
                        ActivityCompat.requestPermissions(this,
                                deniedPermissions.toArray(new String[0]),
                                MULTIPLE_PERMISSIONS_REQUEST_CODE);
                    })
                    .setNegativeButton("取消", null)
                    .show();
        } else {
            ActivityCompat.requestPermissions(this,
                    deniedPermissions.toArray(new String[0]),
                    MULTIPLE_PERMISSIONS_REQUEST_CODE);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == MULTIPLE_PERMISSIONS_REQUEST_CODE) {
        Map<String, Integer> permissionResults = new HashMap<>();
        for (int i = 0; i < permissions.length; i++) {
            permissionResults.put(permissions[i], grantResults[i]);
        }
        List<String> deniedPermissions = new ArrayList<>();
        for (String permission : this.permissions) {
            if (permissionResults.getOrDefault(permission, PackageManager.PERMISSION_DENIED)
                    != PackageManager.PERMISSION_GRANTED) {
                deniedPermissions.add(permission);
            }
        }
        if (deniedPermissions.isEmpty()) {
            startRecording();
        } else {
            boolean doNotAskAgain = false;
            for (String permission : deniedPermissions) {
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                    doNotAskAgain = true;
                    break;
                }
            }
            if (doNotAskAgain) {
                new AlertDialog.Builder(this)
                        .setTitle("权限被拒绝")
                        .setMessage("相机和录音权限被拒绝,无法录制视频,请前往设置中开启权限")
                        .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("取消", null)
                        .show();
            } else {
                Toast.makeText(this, "部分权限被拒绝,无法录制视频", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
重构后代码
private void requestMultiplePermissions() {
    XXPermissions.with(this)
            .permission(PermissionLists.getCameraPermission())
            .permission(PermissionLists.getRecordAudioPermission())
            .description(new PermissionDescription() {
                @Override
                public void askWhetherRequestPermission(@NonNull Activity activity, @NonNull List<IPermission> requestList, @NonNull Runnable continueRequestRunnable, @NonNull Runnable breakRequestRunnable) {
                    new AlertDialog.Builder(activity)
                            .setTitle("权限申请")
                            .setMessage("需要相机和录音权限来录制视频")
                            .setPositiveButton("确定", (dialog, which) -> continueRequestRunnable.run())
                            .setNegativeButton("取消", (dialog, which) -> breakRequestRunnable.run())
                            .show();
                }
            })
            .request(new OnPermissionCallback() {
                @Override
                public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                    if (deniedList.isEmpty()) {
                        startRecording();
                    } else {
                        if (XXPermissions.isDoNotAskAgainPermissions(MainActivity.this, deniedList)) {
                            new AlertDialog.Builder(MainActivity.this)
                                    .setTitle("权限被拒绝")
                                    .setMessage("相机和录音权限被拒绝,无法录制视频,请前往设置中开启权限")
                                    .setPositiveButton("去设置", (dialog, which) -> {
                                        XXPermissions.startPermissionActivity(MainActivity.this, deniedList);
                                    })
                                    .setNegativeButton("取消", null)
                                    .show();
                        } else {
                            Toast.makeText(MainActivity.this, "部分权限被拒绝,无法录制视频", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            });
}
重构说明

重构后的代码通过permission()方法链式添加多个权限,框架内部会自动处理权限的检查和申请。在回调中,通过grantedListdeniedList可以清晰地获取授予和拒绝的权限列表,避免了手动解析权限请求结果的繁琐过程。此外,框架内置的startPermissionActivity()方法可以直接跳转到应用的权限设置页面,简化了权限引导逻辑。

高级功能应用

自定义权限描述与拦截

XXPermissions框架支持通过OnPermissionDescriptionOnPermissionInterceptor接口来自定义权限申请过程中的描述和拦截逻辑,例如在权限申请前显示自定义的说明对话框,或者在权限申请后执行特定的操作。

自定义权限描述

通过实现OnPermissionDescription接口,可以在权限申请前询问用户是否继续申请权限,或者在权限申请过程中显示加载提示等。以下是一个自定义权限描述的示例:

public class CustomPermissionDescription implements OnPermissionDescription {
    @Override
    public void askWhetherRequestPermission(@NonNull Activity activity, @NonNull List<IPermission> requestList, @NonNull Runnable continueRequestRunnable, @NonNull Runnable breakRequestRunnable) {
        // 显示自定义的权限申请说明对话框
        View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_permission_description, null);
        TextView messageTextView = dialogView.findViewById(R.id.tv_message);
        Button confirmButton = dialogView.findViewById(R.id.btn_confirm);
        Button cancelButton = dialogView.findViewById(R.id.btn_cancel);

        // 构建权限说明文本
        StringBuilder message = new StringBuilder();
        message.append("需要以下权限来提供服务:\n");
        for (IPermission permission : requestList) {
            message.append("- ").append(PermissionUtils.getPermissionName(activity, permission)).append("\n");
        }
        messageTextView.setText(message.toString());

        AlertDialog dialog = new AlertDialog.Builder(activity)
                .setView(dialogView)
                .create();

        confirmButton.setOnClickListener(v -> {
            continueRequestRunnable.run();
            dialog.dismiss();
        });

        cancelButton.setOnClickListener(v -> {
            breakRequestRunnable.run();
            dialog.dismiss();
        });

        dialog.show();
    }

    @Override
    public void onRequestPermissionStart(@NonNull Activity activity, @NonNull List<IPermission> requestList) {
        // 权限申请开始,显示加载对话框
        ProgressDialog progressDialog = new ProgressDialog(activity);
        progressDialog.setMessage("正在请求权限...");
        progressDialog.setCancelable(false);
        progressDialog.show();
        activity.getWindow().getDecorView().setTag(R.id.permission_progress_dialog, progressDialog);
    }

    @Override
    public void onRequestPermissionEnd(@NonNull Activity activity, @NonNull List<IPermission> requestList) {
        // 权限申请结束,关闭加载对话框
        ProgressDialog progressDialog = (ProgressDialog) activity.getWindow().getDecorView().getTag(R.id.permission_progress_dialog);
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }
}

在权限请求时,通过description()方法设置自定义的权限描述:

XXPermissions.with(this)
        .permission(PermissionLists.getCameraPermission())
        .permission(PermissionLists.getRecordAudioPermission())
        .description(new CustomPermissionDescription())
        .request(new OnPermissionCallback() {
            // 回调处理逻辑...
        });
自定义权限拦截器

通过实现OnPermissionInterceptor接口,可以在权限申请过程中拦截权限请求,例如记录权限申请日志、检查网络状态等。以下是一个自定义权限拦截器的示例:

public class CustomPermissionInterceptor implements OnPermissionInterceptor {
    @Override
    public void requestPermissions(@NonNull Activity activity, @NonNull List<IPermission> permissions, @NonNull OnPermissionCallback callback) {
        // 记录权限申请日志
        Log.d("PermissionInterceptor", "Requesting permissions: " + permissions);

        // 检查网络状态,如果无网络则不申请权限
        if (!isNetworkAvailable(activity)) {
            Toast.makeText(activity, "无网络连接,无法申请权限", Toast.LENGTH_SHORT).show();
            callback.onResult(new ArrayList<>(), permissions);
            return;
        }

        // 继续执行权限申请
        XXPermissions.with(activity)
                .permission(permissions)
                .request(callback);
    }

    private boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {
            NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
            return activeNetworkInfo != null && activeNetworkInfo.isConnected();
        }
        return false;
    }
}

在Application中全局设置权限拦截器:

public class AppApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 设置全局权限拦截器
        XXPermissions.setPermissionInterceptor(CustomPermissionInterceptor.class);
    }
}

特殊权限申请

XXPermissions框架支持各种特殊权限的申请,如悬浮窗权限、安装未知来源应用权限、通知权限等。以下是几个常见的特殊权限申请示例:

悬浮窗权限申请
XXPermissions.with(this)
        .permission(PermissionLists.getSystemAlertWindowPermission())
        .request(new OnPermissionCallback() {
            @Override
            public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                if (deniedList.isEmpty()) {
                    showFloatingWindow();
                } else {
                    Toast.makeText(MainActivity.this, "悬浮窗权限被拒绝,无法显示悬浮窗", Toast.LENGTH_SHORT).show();
                }
            }
        });

申请悬浮窗权限的效果如图所示:

悬浮窗权限申请

安装未知来源应用权限申请
XXPermissions.with(this)
        .permission(PermissionLists.getRequestInstallPackagesPermission())
        .request(new OnPermissionCallback() {
            @Override
            public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                if (deniedList.isEmpty()) {
                    installApk();
                } else {
                    Toast.makeText(MainActivity.this, "安装权限被拒绝,无法安装应用", Toast.LENGTH_SHORT).show();
                }
            }
        });
通知权限申请(Android 13+)
XXPermissions.with(this)
        .permission(PermissionLists.getPostNotificationsPermission())
        .request(new OnPermissionCallback() {
            @Override
            public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                if (deniedList.isEmpty()) {
                    sendNotification();
                } else {
                    Toast.makeText(MainActivity.this, "通知权限被拒绝,无法发送通知", Toast.LENGTH_SHORT).show();
                }
            }
        });

权限申请结果处理最佳实践

在处理权限申请结果时,需要考虑各种情况,如权限全部授予、部分授予、被永久拒绝等。以下是权限申请结果处理的最佳实践:

检查权限是否全部授予

onResult()回调中,首先检查deniedList是否为空,如果为空则表示所有权限都已授予,可以执行相应的操作。

@Override
public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
    if (deniedList.isEmpty()) {
        // 所有权限都已授予,执行操作
        performAction();
    } else {
        // 处理权限被拒绝的情况
        handleDeniedPermissions(deniedList);
    }
}
判断是否有被永久拒绝的权限

使用XXPermissions.isDoNotAskAgainPermissions()方法判断是否有被用户永久拒绝的权限,如果有,则引导用户前往权限设置页面开启权限。

private void handleDeniedPermissions(List<IPermission> deniedList) {
    if (XXPermissions.isDoNotAskAgainPermissions(this, deniedList)) {
        // 有被永久拒绝的权限,显示对话框引导用户前往设置页面
        new AlertDialog.Builder(this)
                .setTitle("权限被拒绝")
                .setMessage("以下权限被永久拒绝,需要手动开启:\n" + getPermissionNames(deniedList))
                .setPositiveButton("去设置", (dialog, which) -> {
                    XXPermissions.startPermissionActivity(this, deniedList);
                })
                .setNegativeButton("取消", null)
                .show();
    } else {
        // 权限被临时拒绝,提示用户稍后再试
        Toast.makeText(this, "以下权限被拒绝:\n" + getPermissionNames(deniedList), Toast.LENGTH_SHORT).show();
    }
}

private String getPermissionNames(List<IPermission> permissions) {
    StringBuilder names = new StringBuilder();
    for (IPermission permission : permissions) {
        names.append("- ").append(PermissionUtils.getPermissionName(this, permission)).append("\n");
    }
    return names.toString();
}
处理特殊权限的申请结果

某些特殊权限的申请结果需要特殊处理,例如Android 11及以上的存储权限申请,需要判断应用是否适配了分区存储。

@Override
public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
    if (deniedList.isEmpty()) {
        // 检查是否是存储权限
        if (XXPermissions.containsPermission(grantedList, PermissionLists.getManageExternalStoragePermission())) {
            // 检查应用是否适配分区存储
            if (isScopedStorageEnabled()) {
                Toast.makeText(this, "存储权限已授予,可以访问所有文件", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "存储权限已授予,但未适配分区存储", Toast.LENGTH_SHORT).show();
            }
        }
        performAction();
    } else {
        // 处理被拒绝的权限
    }
}

private boolean isScopedStorageEnabled() {
    try {
        ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
        Bundle metaData = applicationInfo.metaData;
        return metaData != null && metaData.getBoolean("ScopedStorage", false);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        return false;
    }
}

常见问题与解决方案

如何适配Android 11及以上的存储权限

Android 11引入了MANAGE_EXTERNAL_STORAGE权限,用于访问外部存储上的所有文件。在使用XXPermissions申请存储权限时,需要注意以下几点:

  1. 在清单文件中注册权限

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    
  2. 申请权限

    XXPermissions.with(this)
            .permission(PermissionLists.getManageExternalStoragePermission())
            .request(new OnPermissionCallback() {
                @Override
                public void onResult(@NonNull List<IPermission> grantedList, @NonNull List<IPermission> deniedList) {
                    if (deniedList.isEmpty()) {
                        Toast.makeText(MainActivity.this, "存储权限已授予", Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(MainActivity.this, "存储权限被拒绝", Toast.LENGTH_SHORT).show();
                    }
                }
            });
    
  3. 适配分区存储

    如果应用适配了分区存储,需要在清单文件中添加以下meta-data:

    <application>
        <meta-data
            android:name="ScopedStorage"
            android:value="true" />
    </application>
    

    这样框架会知道应用已经适配了分区存储,避免进行不必要的权限检查。

如何处理Android 12及以上的内存泄漏问题

XXPermissions框架已经针对Android 12及以上版本的内存泄漏问题进行了优化,通过使用Application Context代替Activity Context来调用PackageManager.shouldShowRequestPermissionRationale方法,避免了内存泄漏。开发者在使用框架时,无需额外处理,只需确保使用的是最新版本的XXPermissions。

如何在Fragment中申请权限

XXPermissions支持在Fragment中申请权限,只需将Fragment实例传递给XXPermissions.with()方法即可。

XXPermissions.with(this) // this 为Fragment实例
        .permission(PermissionLists.getCameraPermission())
        .request(new OnPermissionCallback() {
            // 回调处理逻辑...
        });

如何在后台线程中申请权限

权限申请必须在UI线程中进行,因此如果需要在后台线程中申请权限,需要通过Handler将权限请求逻辑切换到UI线程执行。

new Thread(() -> {
    // 后台线程执行一些操作...
    
    // 需要申请权限,切换到UI线程
    runOnUiThread(() -> {
        XXPermissions.with(MainActivity.this)
                .permission(PermissionLists.getCameraPermission())
                .request(new OnPermissionCallback() {
                    // 回调处理逻辑...
                });
    });
}).start();

总结与展望

XXPermissions框架通过简洁的API设计、全面的版本适配、完善的异常场景处理等特性,为Android权限请求提供了一站式解决方案。通过使用XXPermissions进行代码重构,可以有效减少模板代码、提高版本适配效率、增强应用稳定性。

在未来的开发中,XXPermissions框架将继续跟进Android系统的最新变化,及时适配新的权限模型和特性。同时,框架也将不断优化内部实现,提供更多高级功能,如权限申请数据分析、自定义权限申请UI等,帮助开发者更好地管理应用权限,提升用户体验。

作为开发者,我们应该充分利用XXPermissions等优秀的开源框架,将更多的精力投入到应用的核心功能开发中,提高开发效率和应用质量。同时,也应该关注权限申请的用户体验,合理申请权限,避免过度索取权限导致用户反感。

通过本文的介绍,相信读者已经对XXPermissions框架有了深入的了解,并能够在实际项目中应用该框架进行权限请求代码重构。希望XXPermissions能够成为开发者的得力助手,让权限请求变得更加简单、高效。

XXPermissions框架logo

官方文档:README.md
帮助文档:HelpDoc-zh.md
示例代码:app/src/main/java/com/hjq/permissions/demo/MainActivity.java
权限定义:library/src/main/java/com/hjq/permissions/permission

【免费下载链接】XXPermissions Android 权限请求框架,已适配 Android 14 【免费下载链接】XXPermissions 项目地址: https://gitcode.com/GitHub_Trending/xx/XXPermissions

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值