Android 6.0的RuntimePermission

前言

因为工作的情况,需要解决app在Android 6.0的一些权限问题,所以就此好好研究了一下。

新的运行时权限

Android的权限系统在6.0之前一直都是用户在安装时向用户请求授权的,也因此app在安装后就有可能在用户不知情的情况下获取一些隐私信息。
为了解决这个问题,Google在Android 6.0改进了权限系统,将一些**涉及到用户隐私的权限改为app运行时动态地向用户请求授权 **。这样子不仅可以有效保护用户的隐私,同时也简化了app的安装过程,可谓一举两得。

图片.png

但是对于我们这些开发者而言,就比较麻烦了。因为上图那个提示Dialong是不会自动弹出来的,需要我们去调用相应的代码的。如果我们需要摄像头的权限而没有调用代码去动态获取权限的话:

图片.png

对于不涉及用户隐私的权限我们还是和以前一样只需要在清单文件里添加就可以了,动态申请权限仅在设计用户隐私的权限才需要。那么那些权限涉及到用户的隐私呢?下面是在Android官网找到的:

图片.png

Google将涉及用户隐私的权限分为9个权限组,每组只要有一个权限申请成功了就默认整个权限组的权限都申请成功了。

动态申请权限的步骤

要动态申请权限,首先app的targetSdkVersion就要设置为>=23,接着在清单文件里添加你需要申请的隐私权限:

776.png
//先用摄像头来举例
<uses-permission android:name="android.permission.CAMERA" />

上面两步解决了,现在就可以在代码里面动态申请权限了。这里为了兼容低版本而是用了ActivityCompat这个工具类。

  1. 检查app是否得到了用户授予的隐私权限
    public void click(View view) {
         //检查是否授予了摄像头的权限
         int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
         //如果拥有该权限,permission值此时为PackageManager.PERMISSION_GRANTED(granted翻译过来就是准许)
         //如果没有这个权限,permission值为PackageManager.PERMISSION_DENIED(denied翻译过来就是拒签)
         if (permission != PackageManager.PERMISSION_GRANTED) {
             //没有授予摄像头的权限,那么就去动态地向用户申请该权限
             requestCameraPermission();
         } else {
             //用户已经授权了该权限就直接启动摄像头
             openCamera();
         }
     }
    

..........
//启动系统相机拍照
private void openCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);
}

2. 向用户说明需要授权什么权限,请用户确认

private static final int REQUEST_CAMERA = 0;

private void requestCameraPermission() {
//requestPermissions弹出一个系统的Dialog,说明当前要申请什么权限
//requestPermissions需要一个String数组和一个请求码用于回调
//String数组可以填写多个你需要申请的权限
//需要一个请求码用于处理申请权限的结果
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA);
}


3. 重写<code>onRequestPermissionsResult()</code>方法来处理向用户动态申请权限的结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    //依据请求码来做处理
    if (requestCode == REQUEST_CAMERA) {
        //检查申请的权限是否准许了
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //用户授权了摄像头的权限,这里就可以做我们想做的事情了.
            openCamera();
        } else {
            Toast.makeText(MainActivity.this, "用户拒绝授权摄像机的权限", Toast.LENGTH_SHORT).show();
        }
    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
这样子搞定了。细节什么的,我想我的注释已经写得很清楚了。

####处理不再询问的情况
当我们弄好动态申请权限的调用,第一次弹出系统Dialog是一开始那幅图的样子:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-326448e880b56d2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果用户点了拒绝的话,第二次进入就会多了一次提示:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-7ec490bd6f911c2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果用户点击了不再询问并拒绝了授权,那么用户下次想给app授权时就什么都干不了,因为这个系统Dialog直接就不弹出来了。
查看官方文档的话,会看到官方有提供一个<code>shouldShowRequestPermissionRationale()</code>方法来判断用户是否曾经拒绝过授权。但是我们看下官方文档的下面这个说明:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-744299427cd75d00.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

用户拒绝过app一次授权的话,这个方法将会返回true。但是**如果用户是点击了不再询问并拒绝授权或是手机设备直接禁止授权(比方说在setting的应用页面里面禁止了某个权限)的话,这个方法都是直接返回false的**。
换言之,我们根本没有必要(目前也没有方法)追踪用户是否点击了不再询问。目前还是有一个比较好的处理方法的,就是在用户拒绝授权后提示用户到手机设置里面打开该权限:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    //依据请求码来做处理
    if (requestCode == REQUEST_CAMERA) {
        //检查申请的权限是否标准了
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
           //..............
        } else {
            new AlertDialog.Builder(MainActivity.this)
             .setMessage("你已经拒绝了授权摄像头的权限,可以到手机设置->应用管理->permission->权限里面来进行手动授权才能开启摄像头的功能。\n" +
                            "也可以点击ok按键直接跳到应用权限设置的页面" )
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            openAppInfo();
                        }
                    })
                    .setNegativeButton("Cancel", null)
        }

    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

private void openAppInfo() {
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivity(intent);
}
那么用户拒绝授权后就是下面这个样子:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-84947b87d082981a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这样子用户看到提示后就可以选择按下ok去到应用信息页面来手动设置权限:

![图片.png](http://upload-images.jianshu.io/upload_images/1445840-09cfabcea350d231.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这样子就可以处理国产手机以外的情况。是的,上面的做法对于**国产手机是无效的**。因为**国产手机(小米魅族之类的)都有自己定义的一套权限管理系统**。
一旦你拒绝了授权,那么你就只能点击手机的安全中->权限管理->应用权限管理->自己的app里面来手动打开权限才行。哪怕你已经在setting的应用信息页面里面手动授权了,只要国产手机的安全中心那里没有打开授权,那你的app还是无法得到授权的。

![小米魅族之类的国产手机一旦拒绝了授权只有在安全中心这里手动授权了才可以使用摄像头.png](http://upload-images.jianshu.io/upload_images/1445840-96e4611184c94b53.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####结语
目前市面上的Android6.0手机已经越来越多了,权限适配是每个Android开发者都避免不了的,尤其是国内的开发者,国产手机自己另开炉灶来搞自己那一套权限系统,可以说让我们非常头疼。
如果觉得上面这么一套权限适配的代码写起来很麻烦,也有第三方库来帮助我们来简化代码,这里推荐两个:
[easypermissions,这个是Google推出的](https://github.com/googlesamples/easypermissions)
[PermissionsDispatcher,这个在GitHub上面的star非常多](https://github.com/googlesamples/easypermissions)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容