Android 6.0 动态权限申请

来源:互联网 发布:怎样开淘宝服装网店 编辑:程序博客网 时间:2024/05/20 14:16

简介

Android 6.0 为了保护用户隐私,将一些权限的申请放在了应用运行的时候去申请, 比如以往的开发中,开发人员只需要将需要的权限在清单文件中配置即可,安装后用户可以在设置中的应用信息中看到:XX应用以获取**权限。用户点击可以选择给应用相应的权限。此前的应用权限用户可以选择允许、提醒和拒绝。在安装的时候用户是已经知道应用需要的权限的。但是这样存在一个问题,就是用户在安装的时候,应用需要的权限十分的多(有些开发者为了省事,会请求一些不必要的权限或者请求全部的权限),这个时候用户在安装应用的时候也许并没有发现某些侵犯自己隐私的权限请求,安装之后才发现自己的隐私数据被窃取。谷歌在Android 6.0的系统上,是先安装App,在安装完之后,在使用相关权限的操作时,才会弹出权限的提示框,用户同意授权之后才能正常使用。谷歌这样做,可以让用户更加清醒的认识相关权限的使用,在一定程度上更加人性化和保护了用户的隐私。

谷歌官方将权限分为了两类,一个是正常权限(Normal Permissions),这类权限不涉及用户隐私,是不需要用户进行授权的,比如访问网络,手机震动等。还有一类是危险权限(Dangerous Permissions),一般是涉及到用户隐私的,需要用户进行授权,比如操作SD卡的写入,相机,录音等。

  • 正常权限
ACCESS_LOCATION_EXTRA_COMMANDS      定位权限ACCESS_NETWORK_STATE                网络状态权限ACCESS_NOTIFICATION_POLICY          通知 APP通知显示在状态栏ACCESS_WIFI_STATE                   WiFi状态权限BLUETOOTH                           使用蓝牙权限BLUETOOTH_ADMIN                     控制蓝牙开关BROADCAST_STICKY                    粘性广播CHANGE_NETWORK_STATE                改变网络状态CHANGE_WIFI_MULTICAST_STATE         改变WiFi多播状态,应该是控制手机热点(猜测)CHANGE_WIFI_STATE                   控制WiFi开关,改变WiFi状态DISABLE_KEYGUARD                    改变键盘为不可用EXPAND_STATUS_BAR                   扩展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                  删除快捷方式
  • 危险权限

所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0 (API level 23)或者更高级别的设备中,而且targetSdkVersion>=23时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限权限而直接调用了相关代码,你的App可能就崩溃了,综上所述你需要注意:

  1. 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
  2. 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTS和GET_ACCOUNTS了。
  3. 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示”允许xxx访问设备上的照片、媒体内容和文件吗?

如果App运行在Android 5.1 (API level 22)或者更低级别的设备中,或者targetSdkVersion<=22时(此时设备可以是Android 6.0 (API level 23)或者更高),在所有系统中仍将采用旧的权限管理策略,系统会要求用户在安装的时候授予权限。其次,系统就告诉用户App需要什么权限组,而不是个别的某个权限。

特殊权限组:

CALENDAR    日历CAMERA      相机CONTACTS    联系人LOCATION    定位MICROPHONE  麦克相关,比如录音PHONE       手机状态SENSORS     传感器SMS         短信STORAGE     存储权限

具体的权限分组情况如下表:

  group:android.permission-group.CALENDAR//(日历)    permission:android.permission.READ_CALENDAR    permission:android.permission.WRITE_CALENDAR  group:android.permission-group.CAMERA//(相机)    permission:android.permission.CAMERA  group:android.permission-group.CONTACTS//(联系人)    permission:android.permission.WRITE_CONTACTS    permission:android.permission.GET_ACCOUNTS        permission:android.permission.READ_CONTACTS  group:android.permission-group.LOCATION//(定位)    permission:android.permission.ACCESS_FINE_LOCATION    permission:android.permission.ACCESS_COARSE_LOCATION  group:android.permission-group.MICROPHONE//(麦克风)    permission:android.permission.RECORD_AUDIO  group:android.permission-group.PHONE//(手机)    permission:android.permission.READ_CALL_LOG    permission:android.permission.READ_PHONE_STATE     permission:android.permission.CALL_PHONE    permission:android.permission.WRITE_CALL_LOG    permission:android.permission.USE_SIP    permission:android.permission.PROCESS_OUTGOING_CALLS    permission:com.android.voicemail.permission.ADD_VOICEMAIL  group:android.permission-group.SENSORS//(传感器)    permission:android.permission.BODY_SENSORS  group:android.permission-group.SMS//(短信)    permission:android.permission.READ_SMS    permission:android.permission.RECEIVE_WAP_PUSH    permission:android.permission.RECEIVE_MMS    permission:android.permission.RECEIVE_SMS    permission:android.permission.SEND_SMS    permission:android.permission.READ_CELL_BROADCASTS  group:android.permission-group.STORAGE//(存储)    permission:android.permission.READ_EXTERNAL_STORAGE    permission:android.permission.WRITE_EXTERNAL_STORAGE

使用adb命令可以查看这些需要授权的权限组:

adb shell pm list permissions -d -g

使用adb命令同样可以授权/撤销某个权限:

adb shell pm [grant|revoke] <permission-name>...

关于运行时权限的一些建议

1、只请求你需要的权限,减少请求的次数,或用隐式Intent来让其他的应用来处理。

  1. 如果你使用Intent,你不需要设计界面,由第三方的应用来完成所有操作。比如打电话、选择图片等。
  2. 如果你请求权限,你可以完全控制用户体验,自己定义UI。但是用户也可以拒绝权限,就意味着你的应用不能执行这个特殊操作。

2、防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:

  1. 普通权限(Normal PNermissions):只需要在Androidmanifest.xml中声明相应的权限,安装即许可。
  2. 需要运行时申请的权限(Dangerous Permissions): 必要权限:最好在应用启动的时候,进行请求许可的一些权限(主要是应用中主要功能需要的权限)。附带权限:不是应用主要功能需要的权限(如:选择图片时,需要读取SD卡权限)。

3、解释你的应用为什么需要这些权限:在你调用requestPermissions()之前,你为什么需要这个权限。

  1. 例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在requestpermissions()之前告诉用户你为什么需要这个权限。

4、使用兼容库support-v4中的方法

ContextCompat.checkSelfPermission()ActivityCompat.requestPermissions()ActivityCompat.shouldShowRequestPermissionRationale()

几个重要的方法与常量解释

PackageManager中的两个常量:

  • PackageManager.PERMISSION_DENIED:该权限是被拒绝的。
  • PackageManager.PERMISSION_GRANTED:该权限是被授权的。

Activity中或者Fragment都会有以下几个方法:

int checkSelfPermission(String)void requestPermissions(int, String...)boolean shouldShowRequestPermissionRationale(String)void onRequestPermissionsResult()

上述四个方法中,前三个方法在support-v4的ActivityCompat中都有,建议使用兼容库中的方法。最后一个方法是用户授权或者拒绝某个权限组时系统会回调Activity或者Fragment中的方法。

checkSelfPermission() 检查权限

  1. 检查某一个权限的当前状态,你应该在请求某个权限时检查这个权限是否已经被用户授权,已经授权的权限重复申请可能会让用户产生厌烦。
  2. 该方法有一个参数是权限名称,有一个int的返回值,用这个值与上面提到的两个常量做比较可判断检查的权限当前的状态。
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)        != PackageManager.PERMISSION_GRANTED) {    // 没有权限,申请权限。}else{    // 有权限了,去放肆吧。}

requestPermissions() 申请权限

1、请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,App不能配置和修改这个对话框,如果需要提示用户这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理,该方法有两个参数:

  1. int requestCode,会在回调onRequestPermissionsResult()时返回,用来判断是哪个授权申请的回调。
  2. String[] permissions,权限数组,你需要申请的的权限的数组。

2、由于该方法是异步的,所以无返回值,当用户处理完授权操作时,会回调Activity或者Fragment的onRequestPermissionsResult()方法。

对于Activity我们直接调用requestPermissions(int, String[])即可,不过这个方法是在api leve 23以上,所以我们为了适配可以是使用兼容包提供的方法:

ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MMM);

对于support包的Fragment就可以直接调用requestPermissions(int, String[]),对于app包的Fragment就需要做版本判断了,这样就显得比较麻烦。

onRequestPermissionsResult() 处理权限结果回调

1、该方法在Activity/Fragment中应该被重写,当用户处理完授权操作时,系统会自动回调该方法,该方法有三个参数:

  1. int requestCode,在调用requestPermissions()时的第一个参数。
  2. String[] permissions,权限数组,在调用requestPermissions()时的第二个参数。
  3. int[] grantResults,授权结果数组,对应permissions,具体值和上方提到的PackageManager中的两个常量做比较。
    /**     * 动态申请权限回调     * @param requestCode     * @param permissions     * @param grantResults     */    @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {        switch (requestCode){            case MMM:                if (grantResults.length > 0                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                    //申请权限成功                }else{                    //申请权限失败                }                break;        }    }

shouldShowRequestPermissionRationale()

  1. 望文生义,是否应该显示请求权限的说明。
  2. 第一次请求权限时,用户拒绝了,调用shouldShowRequestPermissionRationale()后返回true,应该显示一些为什么需要这个权限的说明。
  3. 用户在第一次拒绝某个权限后,下次再次申请时,授权的dialog中将会出现“不再提醒”选项,一旦选中勾选了,那么下次申请将不会提示用户。
  4. 第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项,调用shouldShowRequestPermissionRationale()后返回false。
  5. 设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()返回false 。
  6. 加这个提醒的好处在于,用户拒绝过一次权限后我们再次申请时可以提醒该权限的重要性,免得再次申请时用户勾选“不再提醒”并决绝,导致下次申请权限直接失败。

完整代码如下:

    /**     * 动态申请权限     */    public void requestPerm(){        //Android系统大于等于6.0        if(Build.VERSION.SDK_INT>=23){            //检测应用是否具有该权限            if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)                != PackageManager.PERMISSION_GRANTED){                if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)){                                                                                   // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。                } else {                    // 申请授权。                    ActivityCompat.requestPermissions(                        this,                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                        MMM);                }            }        }    }    /**     * 动态申请权限回调     * @param requestCode     * @param permissions     * @param grantResults     */    @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {        switch (requestCode){            case MMM:                if (grantResults.length > 0                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                    //申请权限成功                }else{                    //申请权限失败                }                break;        }    }