Android 6.0 运行时权限处理解析(注解+反射)

Android 6.0 运行时权限处理解析

1.概述

在6.0及以上版本我们的危险权限都需要在运行的时候去申请,之前都是在清单文件中配置即可,现在就不行了需要加代码申请。
对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装。
而在6.0以后,我们可以直接安装,当app需要权限是会给予用户提示用户可以选择同意和拒绝。

2.运行时权限的检测

2.1 差别

新的权限机制更好的保护了用户的隐私,Google将权限分为两类,
一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如访问网络等;
另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、打电话等等。

Dangerous permissions,危险权限都是一组一组的,这是个什么概念呢?
又或是有什么用呢?如果app运行在Android 6.x的机器上,对于授权机制是这样的。
如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。
比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。
此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。

2.2 申请授权。

ContextCompat.checkSelfPermission:检测权限,先判断有没有这个权限。
ActivityCompat.requestPermissions:申请权限

// 打电话权限申请的请求码
private static final int CALL_PHONE_REQUEST_CODE = 0x0011;

// 方法返回值为PackageManager.PERMISSION_DENIED 或者 PackageManager.PERMISSION_GRANTED。
// 当返回GRANTED表示有该权限; DENIED表示没有该权限。
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
    != PackageManager.PERMISSION_GRANTED) {
    // 没有该权限 申请打电话权限
    //  三个参数: 第一个参数是 Context; 第二个参数是用户需要申请的权限字符串数组(可以传多个); 第三个参数是请求码 主要用来处理用户选择的返回结果
    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE}, CALL_PHONE_REQUEST_CODE);
}else {
    // 有该权限,直接打电话
    Intent intent = new Intent(Intent.ACTION_CALL);
    Uri data = Uri.parse("tel:" + 137XXXXXXXX);
    intent.setData(data);
    startActivity(intent);
}

// 如果用户同意或是拒绝那么会回调onRequestPermissionsResult(),别看错了不是onActivityResult()
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,permissions, grantResults);
    if(requestCode == CALL_PHONE_REQUEST_CODE){
        if (grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission Granted  通过  打电话
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri data = Uri.parse("tel:" + 10086);
            intent.setData(data);
            startActivity(intent);
        } else {
            // Permission Denied   被拒绝
            Toast.makeText(this,"权限被拒绝了", Toast.LENGTH_SHORT).show();
        }
    }
}

系统的申请权限,缺点是写代码太多。


3.利用反射加注解的方式封装我们的权限处理框架, 减少代码量。

如何优化代码?

  1. 写到BaseActivity中
  2. 利用反射 + 注解方式。

注解和注释区别
注释: 给程序员看的
注解: 是给程序看的
@Target 代表放在什么位置 FIELD 放在属性上 METHOD 放在方法上 TYPE 放在类上
@Retention 代表什么时候检测 RetentionPolicy.SOURCE 编译时 RetentionPolicy.RUNTIME 运行

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionFail {
    int requestCode(); // 请求码,唯一标识,用于找到对应的方法。
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionSuccess {
    int requestCode(); // 请求码,唯一标识,用于找到对应的方法。
}
/**
 * PermissionHelper.requestPermission(this, CALL_PHONE_REQUEST_CODE, new String[]{Manifest.permission.CALL_PHONE});
 * PermissionHelper.with(this).requestCode(CALL_PHONE_REQUEST_CODE)
 *     .requestPermissions(Manifest.permission.CALL_PHONE).request();
 */
public class PermissionHelper {
    private Object mObject; // 是Activity或Fragment。
    private int mRequestCode;
    private String[] mRequestPermissions;

    private PermissionHelper(Object object) {
        this.mObject = object;
    }

    // 一、传递什么参数,1.Fragment/Activity, 2.请求码, 3.需要请求的权限 String[]
    public static void requestPermission(Activity activity, int requestCode, String[] permissions) {
        PermissionHelper.with(activity).requestCode(requestCode)
                .requestPermissions(permissions)
                .request();
    }
    public static void requestPermission(Fragment fragment, int requestCode, String[] permissions) {
        PermissionHelper.with(fragment).requestCode(requestCode)
                .requestPermissions(permissions)
                .request();
    }

    // 二.链式调用,传递参数。activity,fragment
    public static PermissionHelper with(Activity activity) {
        return new PermissionHelper(activity);
    }
    public static PermissionHelper with(Fragment fragment) {
        return new PermissionHelper(fragment);
    }
    public PermissionHelper requestCode(int requestCode) {
        this.mRequestCode = requestCode;
        return this;
    }
    public PermissionHelper requestPermissions(String... permissions) {
        this.mRequestPermissions = permissions;
        return this;
    }

    // 3.0 真正判断和发起权限的请求。
    public void request() {
        // 3.1 判断是否6.0+,如果不是就直接执行方法,通过反射执行方法
        if (!PermissionUtils.isOverMarshmallow()) {
            // 执行什么方法? 只能通过采用注解的方式给方法打一个标记,然后通过反射去执行方法。直接执行方法。
            PermissionUtils.executeSuccessMethod(mObject, mRequestCode);
            return;
        }

        // 3.2 是大于6.0,先判断权限是否授予?
        List<String> deniedPermissions = PermissionUtils.getDeniedPermission(mObject, mRequestPermissions);
        // 3.2.1 授予了权限,就直接执行,通过反射执行。
        if (deniedPermissions.size() == 0) {
            // 所有权限都曾经授予了。直接执行方法。
            PermissionUtils.executeSuccessMethod(mObject, mRequestCode);
        } else {
            // 3.2.2 没有授予权限,就申请权限。
            // list 转数组。
            String[] permissions = deniedPermissions.toArray(new String[deniedPermissions.size()]);
            ActivityCompat.requestPermissions(PermissionUtils.getActivity(mObject),
                    permissions, mRequestCode);
        }
    }

    /**
     * 处理申请权限的回调 callback
     * @param obj         Activity或Fragment
     * @param requestCode 请求码
     * @param permissions 请求的权限
     */
    public static void requestPermissionsResult(Object obj, int requestCode, String[] permissions) {
        // 再次获取没授权的权限
        List<String> deniedPermissions = PermissionUtils.getDeniedPermission(obj, permissions);
        if (deniedPermissions.size() == 0) {
            // 所有权限都同意了。直接执行方法。
            PermissionUtils.executeSuccessMethod(obj, requestCode);
        } else {
            // 申请的权限中,有用户不同意的
            PermissionUtils.executeFailMethod(obj, requestCode);
        }
    }
}


/**
 * Added by Tom on 2024/07/16.
 * 类里面的方法,都是静态方法。
 * 处理权限请求的工具类。
 */
public class PermissionUtils {
    private PermissionUtils() {
        throw new UnsupportedOperationException("不能被实例化");
    }

    // 判断是否是6.0 以上的版本。
    public static boolean isOverMarshmallow() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    // 执行成功方法。
    public static void executeSuccessMethod(Object reflectObject, int requestCode) {
        // 获取class中所有的方法,遍历是否有 标记,并且要请求码一致。
        Method[] methods = reflectObject.getClass().getDeclaredMethods();
        for (Method method : methods) {
            // 该方法上有没有打这个标记
            PermissionSuccess successMethod = method.getAnnotation(PermissionSuccess.class);
            if (successMethod != null) {
                int methodCode = successMethod.requestCode();
                if (methodCode == requestCode) {
                    // 找到了目标方法,去反射执行该方法。
                    executeMethod(reflectObject, method);
                }
            }
        }
    }

    // 反射执行方法。
    private static void executeMethod(Object reflectObject, Method method) {
        // 执行方法,第一个是该方法属于哪个类, 第二个是参数。
        try {
            method.setAccessible(true); // 临时允许调用私有方法
            method.invoke(reflectObject, new Object[]{});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取未授予的权限(返回没有授予过的权限)
     *
     * @param object Activity或Fragment
     * @return 没有授予过的权限
     */
    public static List<String> getDeniedPermission(Object object, String[] requestPermissions) {
        List<String> deniedPermissions = new ArrayList<>();
        for (String requestPermission : requestPermissions) {
            if (ContextCompat.checkSelfPermission(getActivity(object), requestPermission) ==
                    PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(requestPermission);
            }
        }
        return deniedPermissions;
    }

    public static Activity getActivity(Object object) {
        if (object instanceof Activity) {
            return (Activity) object;
        } else if (object instanceof Fragment) {
            return ((Fragment) object).getActivity();
        }
        return null;
    }

    /**
     * 用户授权失败,执行的方法
     * @param reflectObject  Activity或Fragment
     * @param requestCode
     */
    public static void executeFailMethod(Object reflectObject, int requestCode) {
        // 获取class中所有的方法,遍历是否有 标记,并且要请求码一致。
        Method[] methods = reflectObject.getClass().getDeclaredMethods();
        for (Method method : methods) {
            // 该方法上有没有打这个标记 PermissionFail
            PermissionFail failMethod = method.getAnnotation(PermissionFail.class);
            if (failMethod != null) {
                int methodCode = failMethod.requestCode();
                if (methodCode == requestCode) {
                    // 找到了目标方法,去反射执行该方法。
                    executeMethod(reflectObject, method);
                }
            }
        }
    }
}

// Activity,Fragment中调用使用。

    private static final int CALL_PHONE_REQUEST_CODE = 0x0011;
    public void phoneClick(View view) {
        // PermissionHelper.requestPermission(this, CALL_PHONE_REQUEST_CODE, 
            // new String[]{Manifest.permission.CALL_PHONE});
        PermissionHelper.with(this).requestCode(CALL_PHONE_REQUEST_CODE)
                .requestPermissions(Manifest.permission.CALL_PHONE).request();
    }
    @PermissionSuccess(requestCode = CALL_PHONE_REQUEST_CODE)
    private void callPhone() {
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + "10086");
        intent.setData(data);
        startActivity(intent);
    }
    @PermissionFail(requestCode = CALL_PHONE_REQUEST_CODE)
    private void callPhoneFail() {
        Toast.makeText(this, "您拒绝了拨打电话!", Toast.LENGTH_SHORT).show();
    }
    // 可以放到BaseActivity中。减少代码。
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // super.onRequestPermissionsResult(requestCode, permissions, grantResults);
/*        if(requestCode == CALL_PHONE_REQUEST_CODE){
            if (grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission Granted  通过  打电话
                Intent intent = new Intent(Intent.ACTION_CALL);
                Uri data = Uri.parse("tel:" + 10086);
                intent.setData(data);
                startActivity(intent);
            } else {
                // Permission Denied   被拒绝
                Toast.makeText(this,"权限被拒绝了", Toast.LENGTH_SHORT).show();
            }
        }*/
        PermissionHelper.requestPermissionsResult(this, requestCode, permissions);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335