What's new in Android M - Run Time Permission

一:小小地啰嗦几句

恭喜前公司乔迁北京!不过,也正因为如此,变成了前公司!这段时间又忙找工作,又忙找房子的。可把我给累坏了!当然,这是题外话。

安卓6.0的第一个预览版发布已经不知道多久了。10月6号安卓也推送 android 6.0的正式版。因为向公司申请了一支 Nexus 5,所以我也在第一时间升级到了6.0(话说,这刚买回来的手机,从4.4升级到6.0不知道升级了有没有一天!)。总体体验还不错。

好以,进入正题吧。既然有一个6.0的设备,那么我当然就把 AS 项目的 targetSdkVersion改到了23。可想而知,没有好好阅读官司方文档的我,可是吃了一次大亏。

二:意外的『八阿哥』

项目中有一个要拍照的需求(相信只要是有上传头像的都会用的到哈。)正常情况,我们只要通过一个 Intent,请求打开相机就可以了。代码如下:

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 1001);
在 AndroidMainfest.xml,我们申明下要打开相机的权限。
运行程序,触发上述代码,然后,『八阿哥』出现了。
 
  
可以看到,抛出的异常就是Permission Denial.来看一下完整的描述:

java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE cmp=com.google.android.GoogleCamera/com.android.camera.CaptureActivity } from ProcessRecord{84e7243 12083:com.dinghu.androidpermissiontest/u0a121} (pid=12083, uid=10121) with revoked permission android.permission.CAMERA

可以看出是我们在打开相机应用的时候,出现了异常,导致程序崩溃。但是我们明明在清单文件里已经申明了相机权限。大家可以尝试下,如果将 targetSdkVersion 改成23以下,就可以正常找开相机应用了。

三.权限分类(android 6.0)

既然我们的找码没错,那就是 SDK 6.0的权限机制改变了。好吧,那我们就去看看官网上的 change log.

打开安卓的官网,点开 Banner 后,就可以看到下面的内容了:

点开中间的Requesting Permission at Run Time.跳转之后,可以看到一个关于 android 6.0权限机制的介绍。

如果大家有注意的话,可以知道在6.0之前,我们的安卓应用,在申请权限的时候,大都是在安装的时候,一次性全部授权完的。而在最新的棉花糖版本中,安卓系统把权限进行了分类。分为 Normal Permission 与 Dangerous Permission。不过虽然进行了分类,我们在要调用相关的功能时,还是都要在 AndroidManifest.xml 对相关权限进行申明的。

(原文:On all versions of Android, your app needs to declare both the normal and the dangerous permissions it needs in its app manifest, as described in Declaring Permissions. )

3.1 Normal Permissions

Normal Permissions ,大致上的定义是指,对用户的隐私与安全不会造成很大风险的权限。官方把权限分类到这一级别的权限有:

  • 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
  • UNINSTALL_SHORTCUT
 像以上的权限,只要在清单文件里申明,那么在程序安装的时候,就会被授权。
3.2 Dangerous Permission
在 android 6.0中,所有的 dangerous permission 又属于 permission groups.这里有一个机制要注意一下。就是如果同一个组中的权限中的其中一个被授权了的话,那个同一个组中的其它权限也会被授权。比如,用户请求了一个READ_EXTERNAL_STORAGE 的权限,在授权之后,程序会同时拥有同一个组下面的 WRITE_EXTERNAL_STORAGE.

(原文:If an app requests a dangerous permission listed in its manifest, and the app already has another dangerous permission in the same permission group, the system immediately grants the permission without any interaction with the user. For example, if an app had previously requested and been granted the  READ_CONTACTS permission, and it then requests  WRITE_CONTACTS, the system immediately grants that permission.)
附上官网关于Dangerous Permission 的列表:

 

Dangerous permissions and permission groups.

Permission Group Permissions
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

四:Request Permissions at Run Time

知道了机制之后,我们就要知道申请权限了。在安卓6.0中,权限的授权其实与 IOS 的很像。就是在用到某个权限的时候,进行申请。当然,这里说的是 Dangerous级别的。在新的 SDK 中,android 提供了一个新的方法 checkSelfPermission().这个方法,是先检查该应用是否有对应的权限。

int permissionCheck =checkSelfPermission(Manifest.permission.CAMERA);

根据官方文档,如果应用已经有对应权限,返回的是PackageManage.PERMISSION_GRANTED,否则返回PERMISSION_DENIED.之后,如果没有对应的权限,再调用一下以下函数:

requestPermissions(new String[]{},requestCode)。
那么我们的程序就应该写成:
int status =checkSelfPermission(Manifest.permission.CAMERA);
switch (status) {
    case PackageManager.PERMISSION_GRANTED:
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        startActivityForResult(intent, 1001);
        break;
    default:
        requestPermissions(new String[]{Manifest.permission
.CAMERA},requestCode);
     break;
}
如果程序已经有拍照的权限了,那么我们就直接打开相机,如果没有,就请求权限。在请求完权限之后,我们还要重写一个回调接口:
onRequestPermissionsResult
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)。第一个参数,就是我们请求权限时传入的请求码,第二个参数,是我们请求时的权限列表(因为可以一次性请求多个权限,这里我只演示请求一个权限的情况。)第三个参数,就是对应的权限的请求结果,结果是 PERMISSION_GRANTEDA或者 PERMISSION_DENIED。那么在我们项目中,就应该写成:
 
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 0:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivityForResult(intent, 1001);

            } else {
            }
    }
}
现在运行程序,是不是相机可以正常拍照了。
在第一次请求权限被拒绝之后,再次请求权限,对话框会多一个 not ask agin 的选项。
 
  
如果用户勾选了选项,并且选择了拒绝,之后再进行操作时,APP 会不给任何提示。这显然对用户来说,是非常不好的体验。毕竟做为一个用户,做了操作之后,却没有任何响应。这种做法非常糟糕。
安卓提供了一个方法ActivityCompat.shouldShowRequestPermissionRationale(activity,permission)来检查用户是否拒绝了某一个权限的请求。如果允许请求,那么返回 true,否则,返回 false.所以,我们的代码应该做如下修改:
if(shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
    requestPermissions(new String[]{Manifest.permission
            .CAMERA}, 0);
}
else {
    //Do something to notice user
}
当返回 false 时,我们可以以一个用好的方式(对话框等),告诉用户这个权限被拒绝了,会造成相应的影响。应该让该用户在设置中打开该权限。
当然,还有更好的方式,就是第一次请求时,就告诉用户,这个权限是用来做什么的,也就是对权限进行对应的描述。这里我就不再详细介绍了。有兴趣的朋友可以自行查找相关资料。
五:关于 SDK 版本
细心的读者可能发现,以上介绍的几个方法,大都是在 SDK 23以后才有的。也就是在 SDK 23之前,没有对应方法。那也就是我们在检查权限时,最好加一个判断:
if (Build.VERSION.SDK_INT >= 23) {

    }
}
但是这样会显得很繁琐。所以,推荐 用 android.supporet.v4下的 ActivityCompat.该类支持 SDK 4之后的版本。
那么,我们的代码就换成了:
int status = ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA);
if (status == PackageManager.PERMISSION_GRANTED) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    startActivityForResult(intent, 1001);
} else {
    if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.CAMERA)){
        ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission
                .CAMERA}, 0);
    }
    else {
         Snackbar.make(view, "请在设置中打开相机权限,否则无法使用该功能!", Snackbar.LENGTH_LONG)
        .setAction("Action", null).show();
    }
}
这样修改以后,我们的代码就支持SDK 4以后的版本了。当 SDK 小于23时,只要你在清单文件中声明了对应权限。那么checkSelfPermission
方法返回的值就是 true.而shouldShowRequestPermissionRationale刚在 SDK 小于23时返回 false.
 
附上源码连接:下载源码

 

 

 


你可能感兴趣的:(Android,进阶教程)