一:小小地啰嗦几句
恭喜前公司乔迁北京!不过,也正因为如此,变成了前公司!这段时间又忙找工作,又忙找房子的。可把我给累坏了!当然,这是题外话。
安卓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
Dangerous permissions and permission groups.
Permission Group | Permissions |
CALENDAR |
|
CAMERA |
|
CONTACTS |
|
LOCATION |
|
MICROPHONE |
|
PHONE |
|
SENSORS |
|
SMS |
|
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.
附上源码连接:下载源码