Android6.0权限全解析

Android在 6.0中摒弃了之前的install time permissions model取而代之的是runtime permissions model,也就是动态权限管理
这种改变让用户更加容易的控制自己的隐私,好处不言而喻。但是对于程序员来说,还是有点小负担的,增加了一些学习和开发的成本。

Android M

权限分类

Android 将系统权限分成了四个保护等级:

  • normal :普通级别
  • dangerous :危险级别
  • signature:签名级别
  • signatureOrSystem:系统签名级别

而对于开发而言,关心的只有 普通权限危险权限 两类
其他两级权限,为高级权限,应用拥有platform级别的认证才能申请。

当应用试图在没有权限的情况下做受限操作,应用将被系统杀掉以警示。
所以权限的控制很重要,一个不留神,程序就会系统干掉,后果很严重~~

普通权限 (normal permission)

普通权限 会在App安装期间被默认赋予。这类权限不需要开发人员进行额外操作,这类权限包括:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
FLASHLIGHT
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT

危险权限

这些权限是在开发6.0程序时,必须要注意的。
这些权限处理不好,程序可能会直接被系统干掉。
权限如下:

权限组 权限
CALENDAR READ_CALENDAR,WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS,WRITE_CONTACTS,GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE,CALL_PHONE,READ_CALL_LOG,WRITE_CALL_LOG,ADD_VOICEMAIL,USE_SIP,PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS,RECEIVE_SMS,READ_SMS,RECEIVE_WAP_PUSH,RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE

我们会发现这些权限被分成了组。每个组里面包含了一些相近的权限。

分组的作用:

这些分组实际上是有一些特殊含义的。
系统在动态赋予权利的时候,是按照组去赋予的。即:
如果允许了某一个权限,那么同组中的其他权限也会被直接赋予
对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是对单个权限的说明。

注意:
不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。

代码的处理

  • 权限检查

if (ContextCompat.checkSelfPermission(
        this, 
        Manifest.permission.WRITE_EXTERNAL_STORAGE) 
        != PackageManager.PERMISSION_GRANTED
        ) {
    Toast.makeText(this, "权限拒绝了", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "权限同意了", Toast.LENGTH_SHORT).show();
}
  • 权限申请

权限的申请方法。可以一次申请多个方法。

// 类似 startActivityForResult()中的REQUEST_CODE
int REQUEST_CODE = 99;
// 权限列表,将要申请的权限以数组的形式提交。
// 系统会依次进行弹窗提示。
// 注意:如果AndroidManifest.xml中没有进行权限声明,这里配置了也是无效的,不会有弹窗提示。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
        permissions,
        REQUEST_CODE);
  • 权限回调

这个方法是Activity的一个回调方法。每一次调用 requestPermissions() 方法,都会回调一次这个方法。

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE: {
            // grantResults是一个数组,和申请的数组一一对应。
            // 如果请求被取消,则结果数组为空。
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 权限同意了,做相应处理
            } else {
                // 权限被拒绝了
            }
            return;
        }
    }
}
  • 权限再次申请

当用户拒绝了某个权限时,我们可以再次去申请这个权限。但是这个时候,你应该告诉用户,你为什么要申请这个权限。
系统有一个API可以判断一个权限是否被用户拒绝了。

if (ActivityCompat.shouldShowRequestPermissionRationale(this,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
    // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
}

注意:
这个API有两种情况会返回false:

  1. 用户申请的权限没有被用户拒绝。
  2. 用户在系统权限弹窗中,选中了 不再提醒 选项。
  • 整合代码

看看官方给的代码示例

// 首先检查权限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {
    // 检查用户是否拒绝了这个权限
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
     Manifest.permission.READ_CONTACTS)) {
     // 给出一个提示,告诉用户为什么需要这个权限

    } else {
    // 用户没有拒绝,直接申请权限
    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.READ_CONTACTS},
            MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    //用户授权的结果会回调到FragmentActivity的onRequestPermissionsResult
    }
}else {
 //已经拥有授权
 //TODO: 正常业务逻辑
}
public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                readContacts();
            } else {
             // 权限拒绝了。
            }
            return;
        }
    }
}

注意:
这里有一个问题,就是当用户在弹窗中用户选择了 不再提示
前面也说了,选中了 不再提示 会导致shouldShowRequestPermissionRationale 返回false。
同时系统的权限弹窗也不会再出现了。就算调用代码
ActivityCompat.requestPermissions() 也不会有弹窗。
这里我们就需要自己做一个判断,如果用户选择了 不再提示 (没有API,自己加标记判断吧),我们可以给个提示,并且跳转到设置界面,让用户手动设置,跳转设置界面代码如下:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent, PERMISSIONS_REQUEST_READ_CONTACTS);

  • 示例代码

判断用户是否选择了 不再提示 代码示例:

SharedPreferences sp; // 用来保存配置
private void setFlag(boolean f) {
    SharedPreferences.Editor edit = sp.edit();
    edit.putBoolean("flag", f);
    edit.commit();
}
private boolean getFlag() {
    return sp.getBoolean("flag", false);
}
private boolean dontShowAgain() {
    return getFlag() && !ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
// 检查权限
private void permission() {
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED
            ) {
        // 权限拒绝了
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            // 用户拒绝过权限了,这里给用户个提示。
        } else {
            if (dontShowAgain()) {
                // 用户选择了 “不再提示”。可以跳转到设置界面。
            } else {
                // 用户没有拒绝权限,这时正常申请权限。
                String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
                ActivityCompat.requestPermissions(this,
                    permissions,
                    REQUEST_CODE);
            }
            setFlag(true);
        }
    } else {
        // 权限同意了
    }
}

网上有很多关于权限的封装库。在真正使用的时候可以在网上找一个封装库来使用,会方便很多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容