适配Android6.0动态权限管理

作者: 李晨玮 | 来源:发表于2016-09-10 21:09 被阅读8952次

    Android6.0(M),代号棉花糖,尽管每年谷歌都会很早的推出Andorid新版本,但要让国内(普通用户)真正的用上却需要一段很长的时间,来看下下面这张图:

    android系统市场占有.png

    尽管棉花糖距发布日期已经过去1年多了,但是在市场占有率还是不尽人意。

    由于身边的朋友大多数还停留Android L的系统时代,加上自己也没太在意去适配Android6.0,直到前些时间突然发现公司的项目在Bug收集后台突然出现了不少的java.lang.SecurityException: Permission Denial,经查看原来是修改用户头像那边发生的权限异常,原因是应用没有获取调用相机权限和读写文件权限,再看下用户的操作系统Android6.0.1,也才让我重视到这个问题。

    QQ图片20160910185841.jpg

    权限管理系统的变化

    在Android6.0(M)之前,在用户安装应用的时候会产生一个权限列表,只有用户允许这些权限后,应用才可以正常的安装,这就会产生一个问题,这些权限对用户是不具有感知性的,也就是说用户都不知道你要这些权限干什么,我明明装的是一个阅读类型的应用,你却要我拨打电话的权限,你想干嘛呢?当然绝大部分的开发者是善意的,但也避免不了一些特殊人群利用这些“漏洞”做一些不好的事情。
    而在Android6.0(M)之后,用户是可以不管权限直接安装应用的,当应用需要调用某些权限的时候,会给予用户一个通知与说明,我要这些权限干什么,这样下来可以让用户有更加清醒的权限分配意识,也在一定程度上更加人性化的保护了用户的隐私,避免了“权限一刀切”。

    权限的分组

    在Android6.0(M)之后,对权限进行了分类,大致有这三种:

    • 普通权限
    • 危险权限
    • 特殊权限

    普通权限
    也就是正常权限,是对手机的一些正常操作,对用户的隐私没有太大影响的权限,比如手机的震动,网络访问,蓝牙等权限,这些权限会在应用被安装的时候默认授予,用户不能拒绝,也不能取消。
    普通权限列表:

    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
    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
    UNINSTALL_SHORTCUT

    对于上面这些权限,需要和Android6.0(M)之前的系统,在AndroidManifest.xml声明即可。

    危险权限
    其实就是运行中需要处理的权限,也是我们最需要注意的权限,这些权限会关系到用户的隐私或影响到其他应用的运行,这些危险权限,谷歌还做了一个权限组,以分组的形式来呈现:

    危险权限(权限组).jpg

    由于运行权限机制的出现,变得我们需要对新开发的应用去做适配,当然有人会问,那我之前开发的老应用不就完蛋了,是不是运行到6.0系统上就会发生各种崩溃?
    其实不会的,谷歌做了良好的适配:
    当你的应用targetSdkVersion小于23的时候,就算你运行在Android6.0系统上,它也会默认采用以前的权限管理机制,也就是一刀切。当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它才会采用新的这套权限管理机制。
    所以如果你想逃开这个“麻烦”,只要把targetSdkVersion的版本设置为低于23就可以了,不过不建议采用这种方案,该来的总是要来的,随着国产手机ROM的更新,比如小米,华为等也开始有部分机型进行了系统升级,所以这是种趋势。
    说了这么多,那么来看下怎么进行Android6.0(M)的权限管理适配吧,其实很简单,只需要记住下面几个API方法就可以:(API23之后提供)

    int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
    void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
    void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 请求权限结果回调

    下面来段代码示例(为了向下兼容,这里我采用了v4包下的ContextCompat和ActivityCompat):

            View.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //判断当前系统是否高于或等于6.0
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        //当前系统大于等于6.0
                        if (ContextCompat.checkSelfPermission(MineInforActivity.this,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                            //具有拍照权限,直接调用相机
                            //具体调用代码
                        } else {
                            //不具有拍照权限,需要进行权限申请
                            ActivityCompat.requestPermissions(MineInforActivity.this,new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
                        }
                    } else {
                        //当前系统小于6.0,直接调用拍照
                      
                    }
                }
            });
    

    1.这里PERMISSION_GRANTED表示具有权限,PERMISSION_DENIED表示无权限
    2.在判断应用没有相关权限的后,我们通过requestPermissions进行权限申请,这里的 String[] permissions是个字符串数组,可以对多个权限进行申请
    3.REQUEST_PERMISSION_CAMERA_CODE是个标识码,类似Intent跳转的REQUEST_CODE的,然后我们就可以在onRequestPermissionsResult进行权限申请的回调处理:

        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
                if (grantResults.length >= 1) {
                    int cameraResult = grantResults[0];//相机权限
                    boolean cameraGranted = cameraResult == PackageManager.PERMISSION_GRANTED;//拍照权限
                    if (cameraGranted) {
                      //具有拍照权限,调用相机
                    } else {
                      //不具有相关权限,给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限开启
                    }
                } 
            }
        }
    
    

    上面的那张图,有的朋友应该已经留意到了有个不再提醒的勾选框,如果用户勾选了不再提醒,然后把你拒绝了,那你的应用就GG了,其实这里还有一个API方法:

             if(!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
                                //如果用户勾选了不再提醒,则返回false
                                //给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限打开    
                            }
    

    当用户勾选了不再提醒的框并把你拒之门外了,这个方法的返回值是true,它可以帮助你再一次的提醒用户,你需要这个权限,也是你最后翻身的方法了,哈哈,要坦诚噢~
    但是在实际开发中,不得不说的一个坑,由于国内第三方ROM对系统改造的太严重,比如小米,亲测有些机型的这个方法是不起作用的,永远的是返回false,这个时候该怎么办,就要另外想解决方案了。

    还有需要注意的一点是上面的权限分组,比如读写文件权限:

    WRITE_EXTERNAL_STORAGE
    READ_EXTERNAL_STORAGE
    它们是属于同一个权限组的,你如果拿到了他们其中的一个权限,那么也同时会有另一个权限,同理,如果你拿到读取通讯录的权限,那么你同事也会拥有写入通讯录的权限,这样就避免了我们在申请相关权限的时候需要些老长老长的权限代码了。

    特殊权限
    特殊权限,比如:
    系统级别对话框:SYSTEM_ALERT_WINDOW
    修改系统设置:WRITE_SETTINGS
    这2个特殊权限,我们需要在startActivityForResult里调用即可,这2个权限一般是不会用到,会用到的地方要么是黑科技或者是反用户体验的场景,这里就不再做过多描述,有兴趣的朋友自己探索吧。

    这里需要另外提到的一个权限:READ_PHONE_STATE
    我们可以通过这个权限来获取机器的唯一标识码,很多第三方统计是基于这个标识码来完成统计的,但是在我们应用一开始运行的时候,这个运行权限我们是没有的,在Application里我们也不能对权限进行获取,所以这点也需要我们去注意。

    最后

    对于一些比较特别的权限,比如文件的读写权限,一般在我们第一次开启APP的时候就要去获取了,假设我们一开始没有获取到这个权限,那么如果我的首页有轮播广告图,这个广告图是网络获取的,做了三级缓存,这样就会到导致磁盘缓存无法写入。这边提供一个解决方法,就是在你引导APP启动的时候,就引导用户去获取权限,当用户拒绝的时候,应该给出弹出框并跳转对应的应用权限管理界面(需要对不同机型进行设置)。

    可以参考微信的做法:

    启动app,在闪屏页的时候向用户提出权限的申请

    • 存储空间权限,关闭微信
    • 电话权限,关闭微信
    • 位置权限,关闭微信
      进入app:
    • 发照片时,申请照片权限
    • 发语音时,申请麦克风权限
      用户每次点击拒绝,都弹出自定义对话框,提示用户设置权限

    相关文章

      网友评论

      • developerzjy:请问博主获取机器唯一标识码的权限READ_PHONE_STATE在什么时候获取比较好
        如果在应用启动加载的activity里面的话,有另一个问题,因为我在这个activity里申请了sd的读写卡权限,这两个不同分组的权限能否放在同一个数组里面作为 requestPermissions(String[] permissions, int requestCode)的参数
      • 耑意儿:我这出现了权限已经开放仍旧无法使用该权限的情况😂
        耑意儿: @李晨玮 私有目录?我的情况是用户在app弹出权限请求时已经允许了定位权限,进去手机的权限设置里看也已经是开放了定位权限的。
        李晨玮:@傲娇小野猫 描述下具体的情况,如果是Android7.0另外又加了一个私有目录访问,这个需要注意下。
      • one_cup:但是在实际开发中,不得不说的一个坑,由于国内第三方ROM对系统改造的太严重,比如小米,亲测有些机型的这个方法是不起作用的,永远的是返回false,这个时候该怎么办,就要另外想解决方案了。 问一下你这个另外解决方案是什么方案
        李晨玮:@one_cup 按照现在市面上大部分的APP主流的做法是自定义弹出对话框,告知用户所需要的权限,然后引导用户跳转对应的应用设置界面。
      • 391c813fc64f:我能问一下,如果用户在使用app时去设置界面手动关闭了权限,app该如何处理呢?
        391c813fc64f:@李晨玮 哦,我已经解决了。谢谢你哈。
        李晨玮: @起航_2977 在需要使用相关权限的功能前就应该做权限判断,如果不具备有对应权限,弹出个对话框引导用户去开启权限即可。
      • JJoom:不说我还不知道微信做了6.0的权限处理
      • Ma_小鹏:很清晰 明了
      • 野狗道人闯红灯:清晰明了,通俗易懂
      • 爱吃板栗的小女孩:特意注册登录来给楼主回复。看了好多个人写的,不是少这就少那。以为这个问题要假装瞎看不到呢。哈哈哈哈,还好我没放弃,谢谢楼主!!完美解决
        李晨玮: @爱吃板栗的小女孩 哈哈,解决问题了就好,加油!
      • laer_L:请问最后这一句是放在那里的?shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)
        李晨玮:@laer_L 在判断没有权限的时候,调用它,一般情况下它返回的是true,当用户勾选不再提醒的时候,返回false,但国内定制的ROM,比如小米是永久返回false的。
      • 积木Blocks:厉害,最后的“电话权限,关闭微信”不是很明白。
        李晨玮: @自导自演的机器人 嗯是这样的,阿里旗下的某宝也是采用这种做法。
        积木Blocks:@徐绍殷 这样太霸道了吧 -。-
        徐绍殷:@自导自演的机器人 意思可能是如果用户不给微信权限,那就直接关闭,不让你用了

      本文标题:适配Android6.0动态权限管理

      本文链接:https://www.haomeiwen.com/subject/jdnyettx.html