Android权限请求代码重构: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框架的核心功能模块包括权限定义、权限申请、权限回调、权限拦截器等,各模块的关系如图所示:
- 权限定义:框架将权限封装为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框架,需要按照以下步骤进行操作:
-
添加远程仓库
如果项目的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' } } } -
添加依赖
在app模块的build.gradle文件中添加XXPermissions的依赖:
dependencies { // 权限请求框架:https://gitcode.com/GitHub_Trending/xx/XXPermissions implementation 'com.github.getActivity:XXPermissions:26.5' } -
支持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()方法链式添加多个权限,框架内部会自动处理权限的检查和申请。在回调中,通过grantedList和deniedList可以清晰地获取授予和拒绝的权限列表,避免了手动解析权限请求结果的繁琐过程。此外,框架内置的startPermissionActivity()方法可以直接跳转到应用的权限设置页面,简化了权限引导逻辑。
高级功能应用
自定义权限描述与拦截
XXPermissions框架支持通过OnPermissionDescription和OnPermissionInterceptor接口来自定义权限申请过程中的描述和拦截逻辑,例如在权限申请前显示自定义的说明对话框,或者在权限申请后执行特定的操作。
自定义权限描述
通过实现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申请存储权限时,需要注意以下几点:
-
在清单文件中注册权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> -
申请权限
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(); } } }); -
适配分区存储
如果应用适配了分区存储,需要在清单文件中添加以下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能够成为开发者的得力助手,让权限请求变得更加简单、高效。
官方文档:README.md
帮助文档:HelpDoc-zh.md
示例代码:app/src/main/java/com/hjq/permissions/demo/MainActivity.java
权限定义:library/src/main/java/com/hjq/permissions/permission
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考






