Android 6.0+ 动态权限请求模块,这个模块会包含 权限检查、请求、结果处理 等核心功能,并且支持 单个 / 多个权限请求、权限拒绝后的引导

添加 Android 6.0+ 动态权限请求模块 ,这个模块会包含 权限检查、请求、结果处理 等核心功能,并且支持 单个 / 多个权限请求权限拒绝后的引导,可以直接集成到你的 MVVM 架构中。

一、动态权限模块设计思路

Android 6.0(API 23)后,将权限分为 普通权限 (安装时授予)和 危险权限(运行时动态请求),我们需要:

  1. 封装一个工具类,统一处理权限检查和请求
  2. 在 Activity/Fragment 中接收权限请求结果
  3. 处理 "权限被永久拒绝" 的场景(引导用户去设置页开启)
  4. 适配 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()

七、适配注意事项

  1. Android 13+ 特殊权限 :如 READ_MEDIA_IMAGES(替代 `READ_EX
相关推荐
ganshenml3 小时前
【Android】两个不同版本的jar放进一个工程打成aar会有问题么?
android·java·jar
2501_916008893 小时前
iOS 26 系统流畅度剖析:Liquid Glass 动画表现 + 用户反馈
android·macos·ios·小程序·uni-app·cocoa·iphone
alexhilton11 小时前
灵活、现代的Android应用架构:完整分步指南
android·kotlin·android jetpack
hnlgzb12 小时前
build.gradle中的dependencies 中API
android
xiaguangbo13 小时前
rust slint android 安卓
android·linux·rust
lichong95113 小时前
【大前端++】Android studio Log日志高对比度配色方案
android·java·前端·json·android studio·大前端·大前端++
00后程序员张15 小时前
iOS 开发环境搭建完整指南 Xcode 安装配置、iOS 开发工具选择、ipa 打包与 App Store 上架实战经验
android·macos·ios·小程序·uni-app·iphone·xcode
顾林海16 小时前
揭秘Android编译插桩:ASM让你的代码"偷偷"变强
android·面试·性能优化
雨白16 小时前
初识协程: 为什么需要它以及如何启动第一个协程
android·kotlin