RuntimePermission 运行时权限解析

概述

Android 6.0开始,为了更好的保护用户隐私,引入了新的权限机制:

  • 普通权限与Android 6.0之前一样,直接在Manifest中声明即可
  • 危险权限在使用之前告知用户.获得用户授权之后才可以获取相应的权限
  • 用户可以选择不再提醒永久拒绝某个权限
危险权限列表

粗体字为权限组,权限组下面为权限组内的具体权限</br>
申请权限组中的任何一个权限即获得了整个权限组中的所有权限

CONTACTS
  • READ_CONTACTS
  • WIRTE_CONTACTS
  • GET_CONTACTS
PHONE
  • READ_CALL_LOG
  • READ_PHONE_STATE
  • CALL_PHONE
  • WRITE_CALL_LOG
  • USE_SIP
  • PROCESS_OUTGOING_CALLS
  • ADD_VOICEMAIL
CALENDAR
  • READ_CALENDAR
  • WRITE_CALENDAR
CAMERA

CAMERA

SENSORS

BODY_SENSORS

LOCATION
  • ACCESS_FINE_LOCATION
  • ACCESS_COARSE_LOCATION
STORAGE
  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE
MICROPHONE
  • RECORD_AUDIO
SMS
  • READ_SMS
  • RECEIVE_WAP_PUSH
  • RECEIVE_MMS
  • RECEIVE_SMS
  • SEND_SMS
  • READ_CELL_BROADCASTS

使用安卓自带Api申请运行时权限

以获取用户通讯录为例
  1. 在Manifest中声明我们需要的权限
//in Manifest
<uses-permission android:name="android.permission.READ_CONTACTS"/>
  1. 在Accitity中申请权限
    大致的逻辑:</br>
    -> 检查用户是否授权过该权限
    -> 已经授权过 -> 执行业务逻辑
    -> 没有授权 -> 申请权限 -> 成功 -> 执行业务逻辑
API:
  • 检查用户是否授权过该权限
    int checkSelfPermission()
    返回值:</br>
    PackageManager.PERMISSION_GRANTED 表示有权限
    PackageManager.PERMISSION_DENIED 表示无权限

  • 获取权限
    void requestPermissions()
    三个参数:
    Context
    new String[]{} 需要申请权限的字符串数组
    RequestCode 自定义的请求码,结果回调的方法中使用,判断是哪个权限的申请

  • 权限请求的结果回调
    void onRequestPermissionsResult()

Example:
// 申请一个读取通讯录的权限
private final static int PERMISSION_CONTACT = 1;

private void getPermission() {
    if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
        // 无权限时去请求权限
        ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
    
    }else{
        // 有权限直接进行业务操作
        doSomeWork();
    }
}

/**
* 直接在Activity重写 onRequestPermissionsResult()方法即可
* requestCode requetsPermissions()方法中传入的RequestCode
* String[] permissons  requetsPermissions()方法中传入的permission数组
* grantResults  返回的具体结果
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
    switch(requestCode) {
        case REQUEST_CONTACT:
            if (grantResults.length > 0  && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 授予权限,拨打电话
            doSomeWork();
            } else {
                Toast.makeText(this, "请求权限被拒绝", Toast.LENGTH_SHORT).show();
            }
         break;
    }
}

用户拒绝权限的处理

拒绝的逻辑:
申请权限 -> 用户拒绝 -> END
再次申请权限 -> 权限弹框会多一个不再提醒的选项 -> 用户勾选不再提醒选项并拒绝权限 -> END Forever

为了我们业务的正常进行,我们需要避免 END FOREVER的结果,所以在第二次申请权限的时候我们要提醒用户不要拒绝我们的权限,安卓也为我们提供了这样的API

  • boolean shouldShowRequestPermissionRationale() 是否给用户提醒权限的必要性
在代码中区分是第一次还是被拒绝后再次申请权限
// 重写一下getPermission()方法
private void getPermission() {
    if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
        // 无权限时去请求权限
        if(ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {
            // 通常是弹框告诉用户权限的必要性,然后继续申请权限
            
            new AlertDialog.Builder(this)
                .setCancelable(false)
                .setMessage("求求你别拒绝~")
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
                }
            })
            .setNegativeButton("不给", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            })
            .show();
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
        }
    }else{
        // 有权限直接进行业务操作
        doSomeWork();
    }
}

使用第三方框架申请权限

由于国内的各家手机厂商都对安卓进行了魔改,安卓所提供的原生的API可能会在一些手机上报错,或者无法正常申请权限。对种类繁多的手机Rom和安卓版本都进行适配会非常繁琐,推荐大家使用一些成熟的第三方框架去获取手机权限。

框架并不能解决所有的问题

  • 写博客之前碰到的问题:
    我的App需要申请相机权限,本人用的是小米/华为的手机,当时选择了使用AndPermission申请权限。测试了几次之后都挺正常。然后使用同事的魅族手机测试,在用户第一次拒绝权限之后,第二次请求权限 gradResults 返回的是Granted.所以代码会在没有权限的情况下继续向后执行,相机界面变成了一片黑色.
    然后我又用原生Api尝试了一下,结果是一样的.所以原因应该是魅族手机的权限逻辑问题.
    接下来去找解决问题的方案. 在AndPermission的Issus里面作者是这样解释的

在有些国产机上,申请权限时系统返回的总是有权限,对于通讯录、读写SD卡等权限,AndPermission的策略是拿到有权限的结果后,再执行一下读取一条通讯录或者向SD卡写入一个文件来验证是否真的有权限。但是对于拨打电话此类权限,AndPermission不能真的去拨打一个电话,因此直接回调了onGranted(),开发者可以在onGranted()中直接拨打电话,来配合AndPermission申请权限,如果有就自然拨打出去了,如果没有权限,上述手机会弹出授权对话框或者抛出异常,如果抛出异常,AndPermission会重新回调到onDenied()方法,开发者即可认为是没有权限。

简单分析下作者所说的内容:
通常的逻辑: getPermission -> granted() -> success
严谨的逻辑: getPermission -> granted() -> do() -> 捕获由于权限造成的异常 -> onDenied() -> getPermission()

针对我上面的相机权限问题: 在申请权限成功进入相机界面之后,在相机启动得时候相机打开失败/获取不到相机参数时,重新弹框去告诉用户.所以还是得具体情况具体分析.

最后总结几个申请权限的注意事项:

  1. 在需要的时候申请权限,不要在App开屏的时候请求所有的权限。体验非常不友好,而且容易被拒绝
  2. 对于一些必要权限(用户拒绝概率较高),在申请权限之前弹框提示告知用户.
  3. 有些手机可能会返回错误的权限信息,获取到权限提供的数据才算结束
  4. 参考AndPermission的做法,我们可以在代码中抛出由于没有权限引起的异常,再次申请权限。或者显示ErrorView,让用户点击授权。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容