Android 6.0 在运行时请求权限

1. Android 6.0 在运行时请求权限介绍

从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的设置页面修改权限。

1.1、为什么需要运行时请求权限

iPhone上的App都是默认下载安装的,然后运行App时需要什么权限就弹窗向用户申请,这对用户来说就非常好。因为用户不想给App权限就不给,而Android 6.0以前是这样的,我下载了一个App安装,系统就弹出这个App需要使用的全部的权限,就给我看一下,我需要这个App 的话,只能同意所有的权限都给这个App,要么我不安装这个App。

1.2、 Android权限介绍

系统权限分为两类:正常权限危险权限

正常权限不会直接给用户隐私权带来风险。如果您的应用在其manifest中列出了正常权限,系统将自动授予该权限。

危险权限会授予应用访问用户机密数据的权限。如果您的应用在其manifest中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限,也就是说manifest文件中定义的危险权限将不会随着安装自动授予。

表 1.危险权限和权限组。

权限组权限

CALENDAR :  READ_CALENDARWRITE_CALENDAR

CAMERA  :  CAMERA

CONTACTS  :  READ_CONTACTS  ,  WRITE_CONTACTS  ,  GET_ACCOUNTS

LOCATION :  ACCESS_FINE_LOCATION  ,  ACCESS_COARSE_LOCATION

MICROPHONE :  RECORD_AUDIO

PHONE    READ_PHONE_STATECALL_PHONEREAD_CALL_LOGWRITE_CALL_LOGADD_VOICEMAILUSE_SIPPROCESS_OUTGOING_CALLS

SENSORS  :  BODY_SENSORS

SMS  :  SEND_SMSRECEIVE_SMSREAD_SMSRECEIVE_WAP_PUSHRECEIVE_MMS

STORAGE  :  READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE

从上图中我们可以看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。如果你申请了android.permission.READ_CONTACTS读取联系人的权限,那么6.0 系统就会把这一组中其他的权限也打包给你。我觉得这个和iOS的隐私管理机制非常相似,在iOS系统设置的“隐私->通讯录”中可以看到,如果你给一个App通讯录的权限,那么这个App既可以读也可以写的

Android 6.0里面只有危险权限才需要运行时获取的

1.3、android系统对权限的处理场景分析

如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。

如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的targetSdkVersion是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。

如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion是 22 或更低版本,此时Android系统会把你申请的全部权限都给你用户依然可以进入App的设置界面把权限关闭

例如以下图片中用户在android6.0的版本的设置中把权限关闭,此时你的权限就用不了了。那么程序需要考虑对6.0及以上版本的兼容,具体参考下面的(android.support.v4.content.PermissionChecker)。

值得注意的是Android系统有一套自动权限调整的机制,我们知道android每次sdk升级有可能会加入新的权限,而你的app已经发布到用户手机上安装了,除了升级不可能修改Androidmanifest文件了,此时你可能担心自己的app能够在这些新的sdk版本的手机上运行正常吗,其实android已经考虑了这种场景,Android 将根据为targetSdkVersion属性提供的值决定应用是否需要权限。如果该值低于在其中添加权限的版本,则 Android 会为App自动添加该权限。

例如,API 级别 4 中加入了WRITE_EXTERNAL_STORAGE权限,用以限制访问共享存储空间。如果您的targetSdkVersion为 3 或更低版本,则会向更新 Android 版本设备上的应用添加此权限。


2、如何申请权限

申请权限过程包括以下几个步骤:

检查权限

请求权限

处理权限请求响应

解释应用为什么需要权限

2.1、检查权限

如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机,它不能假设自己今天仍具有该权限,因为用户可能在设置里面关闭了。

要检查您是否具有某项权限,请调用ContextCompat.checkSelfPermission()方法。例如,以下代码段显示了如何检查 Activity 是否具有在日历中进行写入的权限:

// 假设thisActivity是当前屏幕最前端正在和用户交互的activity

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,

        Manifest.permission.WRITE_CALENDAR);

如果应用具有此权限,方法将返回PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回PERMISSION_DENIED,且应用必须明确向用户要求权限。

也可以使用ActivityCompat,它们两个的checkSelfPermission方法是同一个,因为ActivityCompat继承自ContextCompat,而checkSelfPermission是ContextCompat中的方法。

检查权限会有一些特别的问题需要注意,主要有以下两个:

如果App的targetSdkVersion小于23并且运行在Android 6.0系统上,怎么去检测用户关闭了权限呢?

国内有些手机厂商自己定制了手机权限管理(例如:小米),普通的checkSelfPermission已经不准确了,该如何处理?

问题一:

android.support.v4.content.PermissionChecker可以帮我们解决这个问题。这个类的文档是这么描述的:

For apps targeting API lower thanandroid.os.Build.VERSION_CODES.Mthese permissions are always granted as such apps do not expect permission revocations and would crash. Therefore, when the user disables a permission for a legacy app in the UI the platform disables the APIs guarded by this permission making them a no-op which is doing nothing or returning an empty result or default error.

翻译一下是:

当app的targetsdkversion小于23的时候,这些权限默认都会自动给当前app,但如果app没有考虑在6.0设备中被用户主动撤销该权限的场景,那么可能造成app的崩溃。于是app在使用该权限过程中系统权限检查时如果这个权限被用户撤销了,那么对应请求的API会什么都不做或者返回一个空的结果,或者出错。

PermissionChecker.checkSelfPermission方法就是用于检查App自身有没有某一个权限,这个方法的返回结果只有三种:

PERMISSION_GRANTED: 已授权

PERMISSION_DENIED: 没有被授权

PERMISSION_DENIED_APP_OP: 没有被授权

PERMISSION_DENIEDPERMISSION_DENIED_APP_OP都表示没有被授权,但是它们的区别就在于targetSdkVersion的值,如果targetSdkVersion小于23,就返回PERMISSION_DENIED_APP_OP,否则就返回PERMISSION_DENIED

因此,如果你的App的targetSdkVersion小于23,但是运行在Android 6.0及以后的系统上,你可以用下面的代码来检测app是否有某个权限:

PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP


问题二:

国产很多手机在google之前已经做了自己的权限管理,例如小米,所以此时使用ContextCompat的checkSelfPermission方法即便返回PackageManager.PERMISSION_GRANTED 也可能不准确。如果出现这种情况我们需要做一次特殊处理,此时我们需要用到android的隐藏API --AppOpsManager

AppOpsManager官方的解释是系统内部使用,不提供给APP开发者使用。一个小米设备兼容判断的代码如下:

@TargetApi(Build.VERSION_CODES.M)private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {    AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);    String op = AppOpsManager.permissionToOp(permission);    if (!TextUtils.isEmpty(op)) {        int checkOp = appOpsManager.checkOp(op, Process.myUid(), context.getPackageName());        return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;    }    return true;}


2.2、请求权限

如果您的应用需要应用manifest文件中列出的危险权限,那么,它必须要求用户授予该权限。Android 为您提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框,不过,您不能对它们进行自定义。

如果上面的权限检查步骤中结果是应用尚无所需的权限,则应用必须调用一个requestPermissions()方法,以请求适当的权限。应用将传递其所需的权限,以及您指定用于识别此权限请求的整型请求代码。此方法异步运行:它会立即返回,并且在用户响应对话框之后,系统会使用结果调用应用的回调方法,将应用传递的相同请求代码传递到requestPermissions()

提示用户授予或拒绝权限的系统对话框如下:


以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:

// 这里的 thisActivity 是当前屏幕最前端正在和用户交互的activity

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);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS 这个是app定义的整形常量,用于标识一个请求,回调方法中会获得这个请求对应的结果

    }

}

:当您的应用调用requestPermissions()时,系统将向用户显示一个标准对话框。您的应用无法配置或更改此对话框。如果您需要为用户提供任何信息或解释,您应在调用requestPermissions()之前进行,如解释应用为什么需要权限。

2.3、处理权限请求响应

当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的onRequestPermissionsResult()方法,向其传递用户响应。您的应用必须覆写该方法,以了解是否已获得相应权限。回调会将您传递的相同请求代码传递给requestPermissions()。例如,如果应用请求READ_CONTACTS访问权限,则它可能采用以下回调方法:

@Override

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) {

                // 权限已经授权, 很棒!可以继续联系人相关的操作了

            } else {

                // 权限被拒绝了,很糟糕! 禁用和该权限相关的功能

            }

            return;

        }

        // 其它的'case' 代码去处理其它的权限请求回调

    }

}

注意:

当系统要求用户授予权限时,用户可以选择指示系统不再要求提供该权限(即勾选对话框里的不在提示)。这种情况下,无论应用在什么时候使用requestPermissions()再次要求该权限,系统都会立即拒绝此请求。系统会调用您的onRequestPermissionsResult()回调方法,并传递PERMISSION_DENIED

2.4 解释应用为什么需要权限

在某些情况下,您可能需要帮助用户了解您的应用为什么需要某项权限。例如,如果用户启动一个摄影应用,用户对应用要求使用相机的权限可能不会感到吃惊,但用户可能无法理解为什么此应用想要访问用户的位置或联系人。在请求权限之前,不妨为用户提供一个解释。请记住,您不需要通过解释来说服用户;如果您提供太多解释,用户可能发现应用令人失望并将其移除。

您可以采用的一个方法是仅在用户已拒绝某项权限请求时提供解释。如果用户继续尝试使用需要某项权限的功能,但继续拒绝权限请求,则可能表明用户不理解应用为什么需要此权限才能提供相关功能。对于这种情况,比较好的做法是显示解释。

为了帮助查找用户可能需要解释的情形,Android 提供了一个实用程序方法,即shouldShowRequestPermissionRationale()。如果应用之前请求过此权限但用户拒绝了请求,此方法将返回true

:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了Don't ask again选项,此方法将返回false。如果设备规范禁止应用具有该权限,此方法也会返回false

That's all 感谢阅读,原文地址http://www.huahuaxie.com/android-6-0-runtime-permission/

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