三十二、关于动态权限申请的必备知识

概述

在安卓6.0以前,安卓应用的权限会在安装阶段向用户展示。但是在使用应用的阶段不需要再申请任何权限。

而在6.0以后,普通股权限只需要在清单文件里注册即可,但是危险权限,则除了注册在清单里之外,还需要在使用app时动态申请。

权限分类

普通权限

在程序运行时自动获取,只需要在清单文件里声明即可。比如 internet。

危险权限

App在一些操作中需要访问用户的隐私信息,比如图库,通讯录。此时安卓系统就会向用户展示所需的权限。

完整的权限申请流程

图示

解释说明

    1. 判断sdk版本是否低于23,如果是,则不需要申请权限,流程结束。否则往下。
    1. 调用checkPermission 判断 权限是否已申请成功,如果返回true,流程结束。否则往下。
    1. 调用requestPermission 向系统主动发送申请权限的操作。

但是在 requestPermission 之前,有一个 shouldShowRequestPermissionRationale 的判断

关键的 shouldShowRequestPermissionRationale

  • 此判断如果返回了 true,则说明,之前app已经尝试申请过权限,但是用户点击了拒绝,但是并没有选择 Never ask again(以后不再提示).

这表示,用户没有永久拒绝权限,app还能再次调用 requestPermission尝试申请。

  • 如果返回了 false, 有两种情况:
    • app从来没有申请过此权限
      • 此时,只需要 再次调用 requestPermission尝试申请
    • app申请过此权限,但是被用户拒绝,并且勾选了 Never ask again(永久拒绝)
      • 此时,只能弹出自定义对话框,提示用户此操作必须具备权限之后才能继续,并且在点击某个按钮之后进入到 app的权限管理页面。

特别注意 ,以上返回true的情况,在某些国产手机上被屏蔽,比如华为,小米,也就是说,这些手机上动态申请权限,只有两种情况,允许 ,或者永久拒绝, 不存在介于两者中间的 暂时拒绝的选项。

标准代码示范

通常,我们需要按照 activity.requestPermissions() 传入 permissions权限数组,以及 requestCode回调码,然后在Activity内进行回调处理。

但是现在有了更优雅的方式,支持我们在 任意代码的位置,不限定activity内,支持fragment,service,甚至一个普通类内进行处理。

基本思想为: 开启一个透明不可见的Activity作为申请权限的载体,申请的动作,以及 申请后的回调 全部放在此Activity内。主要代码如下:

权限申请结果接口

java 复制代码
/**
 * 权限申请结果接口
 */
public interface IPermissionCallback {

    /**
     * 授予权限
     */
    void granted(int requestCode);

    /**
     * 这次拒绝,但是并没有勾选"以后不再提示"
     */
    void denied(int requestCode);

    /**
     * 勾选"以后不再提示",并且拒绝
     */
    void deniedForever(int requestCode);
}

权限基本功能类

java 复制代码
public class PermissionUtil {


    /**
     * 判断所有权限是否都同意了,都同意返回true 否则返回false
     *
     * @param context     context
     * @param permissions permission list
     * @return return true if all permissions granted else false
     */
    public static boolean hasSelfPermissions(Context context, String... permissions) {
        for (String permission : permissions) {
            if (!hasSelfPermission(context, permission)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断单个权限是否同意
     *
     * @param context    context
     * @param permission permission
     * @return return true if permission granted
     */
    private static boolean hasSelfPermission(Context context, String permission) {
        return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * 检查是否都赋予权限
     *
     * @param grantResults grantResults
     * @return 所有都同意返回true 否则返回false
     */
    public static boolean verifyPermissions(int... grantResults) {
        if (grantResults.length == 0) return false;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检查所给权限List是否需要给提示
     *
     * @param activity    Activity
     * @param permissions 权限list
     * @return 如果某个权限需要提示则返回true
     */
    public static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) {
        for (String permission : permissions) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                return true;
            }
        }
        return false;
    }


}

权限申请核心类,唯一入口

java 复制代码
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import org.jetbrains.annotations.Nullable;

public class PermissionAspectActivity extends Activity {


    private final static String permissionsTag = "permissions";
    private final static String requestCodeTag = "requestCode";

    private static IPermissionCallback mCallback;

    /**
     * 启动当前这个Activity
     */
    public static void startActivity(Context context, String[] permissions, int requestCode, IPermissionCallback callback) {
        Log.d("PermissionAspectTag", "context is : " + context.getClass().getSimpleName());
        if (context == null) return;
        mCallback = callback;
        //启动当前这个Activity并且取消切换动画
        Intent intent = new Intent(context, PermissionAspectActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);//开启新的任务栈并且清除栈顶...为何要清除栈顶
        intent.putExtra(permissionsTag, permissions);
        intent.putExtra(requestCodeTag, requestCode);

        context.startActivity(intent);//利用context启动activity

        if (context instanceof Activity) {//并且,如果是activity启动的,那么还要屏蔽掉activity切换动画
            ((Activity) context).overridePendingTransition(0, 0);
        }
    }


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        String[] permissions = intent.getStringArrayExtra(permissionsTag);
        int requestCode = intent.getIntExtra(requestCodeTag, 0);

        if (PermissionUtil.hasSelfPermissions(this, permissions)) {
            mCallback.granted(requestCode);
            finish();
            overridePendingTransition(0, 0);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(permissions, requestCode);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        // 现在拿到了权限的申请结果,那么如何处理,我这个Activity只是为了申请,然后把结果告诉外界,所以结果的处理只能是外界传进来
        boolean granted = PermissionUtil.verifyPermissions(grantResults);
        if (granted) {//如果用户给了权限
            mCallback.granted(requestCode);
        } else {
            if (PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) { // 判断是否还能继续申请
                mCallback.denied(requestCode);
            } else { // 不能再次申请,用户已经完全拒绝
                mCallback.deniedForever(requestCode);
            }
        }
        this.finish();
        overridePendingTransition(0, 0);

    }
}

注意,此 PermissionAspectActivity 必须在清单文件中注册

而且它的风格是透明的:

效果如下,任意类中都能使用权限申请:

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch8 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光8 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   8 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   8 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web8 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery