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

概述

在安卓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 必须在清单文件中注册

而且它的风格是透明的:

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

相关推荐
未来之窗软件服务11 小时前
一体化系统(九)智慧社区综合报表——东方仙盟练气期
大数据·前端·仙盟创梦ide·东方仙盟·东方仙盟一体化
陈天伟教授15 小时前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看15 小时前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai16 小时前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端
苏打水com16 小时前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅16 小时前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com16 小时前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger16 小时前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite
BD_Marathon17 小时前
【JavaWeb】路径问题_前端绝对路径问题
前端