添加 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 {
@Override
protected 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 处理)
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionUtils.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
/**
* 从应用设置页返回的结果
*/
@Override
protected 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();
}
// ====================== 权限回调实现 ======================
/**
* 所有权限都授予(子类可重写处理具体业务)
*/
@Override
public void onPermissionGranted(String[] grantedPermissions) {
// 示例:默认提示"权限已授予"
Toast.makeText(this, "所需权限已授予", Toast.LENGTH_SHORT).show();
}
/**
* 权限被拒绝(子类可重写处理具体业务)
* @param isPermanentlyDenied 是否永久拒绝(用户勾选"不再询问")
*/
@Override
public 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();
}
}
// ====================== 避免内存泄漏 ======================
@Override
protected 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
};
@Override
protected int getLayoutId() {
// 返回当前 Activity 的布局 ID
return R.layout.activity_main;
}
@Override
protected void initView() {
// 初始化 ViewModel
mainViewModel = new MainViewModel();
// 初始化按钮(点击触发权限请求)
Button btnRequestPermission = findViewById(R.id.btn_request_permission);
btnRequestPermission.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击按钮,发起权限请求
requestMultiplePermissions(REQUIRED_PERMISSIONS);
}
});
}
@Override
protected void initData() {
// 初始化数据(如加载列表、请求接口等)
}
/**
* 重写:权限全部授予后,调用 ViewModel 处理业务(如打开相机)
*/
@Override
public void onPermissionGranted(String[] grantedPermissions) {
super.onPermissionGranted(grantedPermissions);
// 权限授予成功,通知 ViewModel 执行业务逻辑(如打开相机)
mainViewModel.openCamera(this);
}
/**
* 重写:从设置页返回后,重新检查权限
*/
@Override
protected 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" />
<application
android: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