适配Android6.0动态权限管理

144
作者 李晨玮
2016.09.10 21:09 字数 2203

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:
  • 发照片时,申请照片权限
  • 发语音时,申请麦克风权限
    用户每次点击拒绝,都弹出自定义对话框,提示用户设置权限
Android开发