Android6.0权限全解析

作者: Pan_大宝 | 来源:发表于2016-12-19 17:49 被阅读1688次

    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 {
            // 权限同意了
        }
    }
    

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

    相关文章

      网友评论

      • 陆地蛟龙:有没有遇到过,某些应用商店提醒, 此应用属于危险应用,或者会危害你的设备 这样提示。
        这种情况是申请了一些危险权限导致的吗?但是有的权限又不得不申请。这种如何规避?或者有没有更好的方法?
        Pan_大宝: @胡髭蛤蟆 这个不清楚!
      • snowdream:总结的不错
      • DylanW:”// 用户拒绝过权限了,这里给用户个提示。“,请教一下这里的处理是不是和正常申请一样就可以了?
        Pan_大宝:@AndSync 我觉得你的方法确实很方便。 :+1: 。有点小瑕疵就是:如果用户点击了“拒绝”按钮,没有选择“不再提示”。这个时候其实是可以再次弹出权限弹框的,而不必跳转到设置界面去操作。你认为呢?咱们可以讨论讨论。 :smile:
        SnapKit:我的觉得不管用户是否拒绝过,每次都去申请,如果成功就会走成功的逻辑,如果失败弹出dialog 点确定跳转到设置引导用户去打开权限,当然在6.0以下 都会走成功的逻辑,这时候有些权限是需要采取非正常的手段去判断是否获取了权限,比如相机,录音等http://www.jianshu.com/p/4a60b064a0ab之前我写的,欢迎指教 :smile:
        Pan_大宝:@DylanW 最好弹个dialog给个提示,一个取消按钮,一个再次申请按钮,再次申请的监听事件中去再次申请权限

      本文标题:Android6.0权限全解析

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