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

概述

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

而且它的风格是透明的:

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

相关推荐
LHX sir3 小时前
什么是UIOTOS?
前端·前端框架·编辑器·团队开发·个人开发·web
Gazer_S3 小时前
【前端状态管理技术解析:Redux 与 Vue 生态对比】
前端·javascript·vue.js
小光学长3 小时前
基于Vue的图书馆座位预约系统6emrqhc8(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
Y学院3 小时前
vue的组件通信
前端·javascript·vue.js
PairsNightRain3 小时前
React Concurrent Mode 是什么?怎么使用?
前端·react.js·前端框架
小岛前端4 小时前
React 剧变!
前端·react.js·前端框架
teeeeeeemo4 小时前
Webpack 模块联邦(Module Federation)
开发语言·前端·javascript·笔记·webpack·node.js
岁月宁静5 小时前
AI聊天系统 实战:打造优雅的聊天记录复制与批量下载功能
前端·vue.js·人工智能
小小弯_Shelby5 小时前
uniApp App内嵌H5打开内部链接,返回手势(左滑右滑页面)会直接关闭H5项目
前端·uni-app
IT_陈寒5 小时前
SpringBoot性能飞跃:5个关键优化让你的应用吞吐量提升300%
前端·人工智能·后端