Android 6.0+ 动态权限请求模块,这个模块会包含 权限检查、请求、结果处理 等核心功能,并且支持 单个 / 多个权限请求、权限拒绝后的引导
添加 Android 6.0+ 动态权限请求模块,这个模块会包含 权限检查、请求、结果处理 等核心功能,并且支持 单个 / 多个权限请求、权限拒绝后的引导,可以直接集成到你的 MVVM 架构中。
一、动态权限模块设计思路
Android 6.0(API 23)后,将权限分为 普通权限(安装时授予)和 危险权限(运行时动态请求),我们需要:
- 封装一个工具类,统一处理权限检查和请求
- 在 Activity/Fragment 中接收权限请求结果
- 处理 “权限被永久拒绝” 的场景(引导用户去设置页开启)
- 适配 MVVM 架构,让 ViewModel 可间接触发权限请求
二、第一步:添加权限工具类 PermissionUtils
创建 com.nyw.mvvmmode.utils.PermissionUtils.java
,封装核心逻辑:
package com.nyw.mvvmmode.utils;import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.util.ArrayList;
import java.util.List;/*** Android 6.0+ 动态权限工具类* 支持:单个/多个权限请求、权限检查、引导用户去设置页开启权限*/
public class PermissionUtils {// 权限请求码(自定义,确保唯一)public static final int REQUEST_CODE_PERMISSION = 100;// 跳转到应用设置页的请求码public static final int REQUEST_CODE_SETTINGS = 101;/*** 检查单个权限是否已授予*/public static boolean checkSinglePermission(Context context, @NonNull String permission) {// Android 6.0 以下默认授予所有权限if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {return true;}return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;}/*** 检查多个权限是否已全部授予*/public static boolean checkMultiplePermissions(Context context, @NonNull String[] permissions) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || permissions == null || permissions.length == 0) {return true;}for (String permission : permissions) {if (!checkSinglePermission(context, permission)) {return false;}}return true;}/*** 筛选出未授予的权限*/public static List<String> getDeniedPermissions(Context context, @NonNull String[] permissions) {List<String> deniedPermissions = new ArrayList<>();if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || permissions == null || permissions.length == 0) {return deniedPermissions;}for (String permission : permissions) {if (!checkSinglePermission(context, permission)) {deniedPermissions.add(permission);}}return deniedPermissions;}/*** 请求单个权限*/public static void requestSinglePermission(Activity activity, @NonNull String permission) {requestMultiplePermissions(activity, new String[]{permission});}/*** 请求多个权限*/public static void requestMultiplePermissions(Activity activity, @NonNull String[] permissions) {if (activity == null || permissions == null || permissions.length == 0) {return;}// 筛选未授予的权限List<String> deniedPermissions = getDeniedPermissions(activity, permissions);if (deniedPermissions.isEmpty()) {// 所有权限已授予,直接回调成功if (sPermissionCallback != null) {sPermissionCallback.onPermissionGranted(permissions);}return;}// 发起权限请求ActivityCompat.requestPermissions(activity,deniedPermissions.toArray(new String[0]),REQUEST_CODE_PERMISSION);}/*** 检查是否需要显示权限说明(用户之前拒绝过权限)*/public static boolean shouldShowPermissionRationale(Activity activity, @NonNull String permission) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {return false;}return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);}/*** 跳转到应用设置页(用户永久拒绝权限时调用)*/public static void goToAppSettings(Activity activity) {if (activity == null) {return;}Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", activity.getPackageName(), null);intent.setData(uri);activity.startActivityForResult(intent, REQUEST_CODE_SETTINGS);}// ====================== 权限请求回调 ======================private static PermissionCallback sPermissionCallback;public interface PermissionCallback {/*** 所有请求的权限都已授予*/void onPermissionGranted(String[] grantedPermissions);/*** 部分/全部权限被拒绝*/void onPermissionDenied(String[] deniedPermissions, boolean isPermanentlyDenied);}/*** 设置权限请求回调(需在请求权限前设置)*/public static void setPermissionCallback(PermissionCallback callback) {sPermissionCallback = callback;}/*** 清除权限回调(避免内存泄漏)*/public static void clearPermissionCallback() {sPermissionCallback = null;}/*** 在 Activity 的 onRequestPermissionsResult 中调用,处理权限请求结果*/public static void onRequestPermissionsResult(Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {if (requestCode != REQUEST_CODE_PERMISSION || sPermissionCallback == null) {return;}List<String> grantedPermissions = new ArrayList<>();List<String> deniedPermissions = new ArrayList<>();boolean isPermanentlyDenied = false;for (int i = 0; i < permissions.length; i++) {if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {grantedPermissions.add(permissions[i]);} else {deniedPermissions.add(permissions[i]);// 检查是否永久拒绝(用户勾选了“不再询问”)if (!shouldShowPermissionRationale(activity, permissions[i])) {isPermanentlyDenied = true;}}}if (deniedPermissions.isEmpty()) {// 所有权限都授予sPermissionCallback.onPermissionGranted(grantedPermissions.toArray(new String[0]));} else {// 部分/全部权限被拒绝sPermissionCallback.onPermissionDenied(deniedPermissions.toArray(new String[0]), isPermanentlyDenied);}}
}
三、第二步:在 BaseActivity 中集成权限处理
为了让所有 Activity 都能复用权限功能,创建 com.nyw.mvvmmode.ui.base.BaseActivity.java
,统一处理权限请求和结果:
package com.nyw.mvvmmode.ui.base;import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.nyw.mvvmmode.utils.PermissionUtils;/*** 基础 Activity,封装权限处理、生命周期管理等通用功能*/
public abstract class BaseActivity extends AppCompatActivity implements PermissionUtils.PermissionCallback {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 设置布局(由子类实现)setContentView(getLayoutId());// 初始化视图(由子类实现)initView();// 初始化数据(由子类实现)initData();}/*** 获取布局 ID(子类必须实现)*/protected abstract int getLayoutId();/*** 初始化视图(子类按需实现)*/protected abstract void initView();/*** 初始化数据(子类按需实现)*/protected abstract void initData();// ====================== 权限相关方法 ======================/*** 请求单个权限*/protected void requestSinglePermission(String permission) {PermissionUtils.setPermissionCallback(this);PermissionUtils.requestSinglePermission(this, permission);}/*** 请求多个权限*/protected void requestMultiplePermissions(String[] permissions) {PermissionUtils.setPermissionCallback(this);PermissionUtils.requestMultiplePermissions(this, permissions);}/*** 权限请求结果回调(交给 PermissionUtils 处理)*/@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);PermissionUtils.onRequestPermissionsResult(this, requestCode, permissions, grantResults);}/*** 从应用设置页返回的结果*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == PermissionUtils.REQUEST_CODE_SETTINGS) {// 返回设置页后,重新检查权限(子类可重写此方法处理具体逻辑)onPermissionSettingsBack();}}/*** 从设置页返回后,重新检查权限(子类按需重写)*/protected void onPermissionSettingsBack() {// 示例:默认提示“请开启所需权限”Toast.makeText(this, "请开启应用所需权限以正常使用", Toast.LENGTH_SHORT).show();}// ====================== 权限回调实现 ======================/*** 所有权限都授予(子类可重写处理具体业务)*/@Overridepublic void onPermissionGranted(String[] grantedPermissions) {// 示例:默认提示“权限已授予”Toast.makeText(this, "所需权限已授予", Toast.LENGTH_SHORT).show();}/*** 权限被拒绝(子类可重写处理具体业务)* @param isPermanentlyDenied 是否永久拒绝(用户勾选“不再询问”)*/@Overridepublic void onPermissionDenied(String[] deniedPermissions, boolean isPermanentlyDenied) {if (isPermanentlyDenied) {// 永久拒绝:引导用户去设置页开启Toast.makeText(this, "权限已被永久拒绝,请在设置中开启", Toast.LENGTH_SHORT).show();PermissionUtils.goToAppSettings(this);} else {// 临时拒绝:提示用户需要权限Toast.makeText(this, "请授予必要权限以正常使用功能", Toast.LENGTH_SHORT).show();}}// ====================== 避免内存泄漏 ======================@Overrideprotected void onDestroy() {super.onDestroy();// 清除权限回调,避免内存泄漏PermissionUtils.clearPermissionCallback();}
}
四、第三步:在 MVVM 架构中使用动态权限
以 MainActivity 中请求 “相机 + 存储权限” 为例,演示如何在 View(Activity)和 ViewModel 中配合使用权限模块:
1. View 层(MainActivity)
继承 BaseActivity,发起权限请求并处理业务逻辑:
package com.nyw.mvvmmode.ui.activity;import android.os.Bundle;
import android.view.View;
import android.widget.Button;import com.nyw.mvvmmode.R;
import com.nyw.mvvmmode.ui.base.BaseActivity;
import com.nyw.mvvmmode.viewmodel.MainViewModel;/*** 示例:在 View 层发起权限请求,配合 ViewModel 处理业务*/
public class MainActivity extends BaseActivity {private MainViewModel mainViewModel;// 需要请求的权限(相机 + 读写存储)private final String[] REQUIRED_PERMISSIONS = {android.Manifest.permission.CAMERA,android.Manifest.permission.READ_EXTERNAL_STORAGE,android.Manifest.permission.WRITE_EXTERNAL_STORAGE};@Overrideprotected int getLayoutId() {// 返回当前 Activity 的布局 IDreturn R.layout.activity_main;}@Overrideprotected void initView() {// 初始化 ViewModelmainViewModel = new MainViewModel();// 初始化按钮(点击触发权限请求)Button btnRequestPermission = findViewById(R.id.btn_request_permission);btnRequestPermission.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 点击按钮,发起权限请求requestMultiplePermissions(REQUIRED_PERMISSIONS);}});}@Overrideprotected void initData() {// 初始化数据(如加载列表、请求接口等)}/*** 重写:权限全部授予后,调用 ViewModel 处理业务(如打开相机)*/@Overridepublic void onPermissionGranted(String[] grantedPermissions) {super.onPermissionGranted(grantedPermissions);// 权限授予成功,通知 ViewModel 执行业务逻辑(如打开相机)mainViewModel.openCamera(this);}/*** 重写:从设置页返回后,重新检查权限*/@Overrideprotected void onPermissionSettingsBack() {super.onPermissionSettingsBack();// 重新检查权限,如果已授予则执行业务if (PermissionUtils.checkMultiplePermissions(this, REQUIRED_PERMISSIONS)) {mainViewModel.openCamera(this);}}
}
2、’ViewModel 层(MainViewModel)
处理业务逻辑(如打开相机),不直接操作权限:
package com.nyw.mvvmmode.viewmodel;import android.app.Activity;
import android.content.Intent;
import android.provider.MediaStore;import androidx.lifecycle.ViewModel;/*** 示例:ViewModel 处理业务逻辑,不直接操作权限*/
public class MainViewModel extends ViewModel {/*** 打开相机(权限已在 View 层授予)*/public void openCamera(Activity activity) {// 跳转到系统相机(业务逻辑)Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);activity.startActivityForResult(intent, 1002); // 1002 是相机请求码}// 其他业务逻辑...
}
五、第四步:在 AndroidManifest 中声明权限
无论是否需要动态请求,都必须在 AndroidManifest.xml
中声明权限(否则动态请求也会失败):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.nyw.mvvmmode"><!-- 声明需要的权限(示例:相机、存储) --><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 之前的网络权限 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.INTERNET" /><applicationandroid:name=".MyApplication"android:allowBackup="true"android:label="@string/app_name"android:supportsRtl="true"android:theme="@style/AppTheme"> <!-- 之前设置的自定义主题 --><activity android:name=".ui.activity.MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
六、核心功能说明
功能 | 实现方式 | 调用示例 |
---|---|---|
单个权限请求 | requestSinglePermission(Manifest.permission.CAMERA) | 打开相机前请求相机权限 |
多个权限请求 | requestMultiplePermissions(new String[]{CAMERA, READ_EXTERNAL_STORAGE}) | 拍照并保存时请求相机 + 存储权限 |
权限检查 | PermissionUtils.checkSinglePermission(context, permission) | 初始化时检查是否已有权限 |
永久拒绝处理 | 引导用户去设置页(PermissionUtils.goToAppSettings(activity) ) | 用户勾选 “不再询问” 后触发 |
内存泄漏防护 | 在 onDestroy 中清除权限回调 | PermissionUtils.clearPermissionCallback() |
七、适配注意事项
- Android 13+ 特殊权限:如
READ_MEDIA_IMAGES
(替代 `READ_EX