系统权限简介
Android出于系统稳定性以及用户隐私方面的考虑,将应用程序访问权限限制在各自的沙盒内。程序可以随意访问所在沙盒内部的资源或者信息,访问沙盒外部的则必须明确的申请相关访问权限。应用程序所需要的权限需要在AndroidManifest.xml
文件中申明。如:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.douyoumi.permission"> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application ...> ... </application></manifest>
系统权限根据敏感程度分为普通权限 和 危险权限两类。两类权限都需要在AndroidManifest.xml
文件中申明。在Android 5.1 (API level 22) 及其以下版本上,系统在APP安装时要求用户授权所有权限,否则APP不能安装;而在Android 6.0及其以上版本上,系统在APP安装时授权所有普通权限,危险权限需要在使用时动态让用户授权。这使得Android的权限管理更加灵活,用户可以根据需要在设置应用中对应用的各个危险权限授予不同的权限。Android系统的权限管理不知道被多少人吐槽过,这一改进无疑是加分项。
权限申请官方开发指导
Android 6.0上使用在AndroidManifest.xml中已经申明的危险权限时需要用户授权。针对权限请求相关操作系统提供了三个API。下面结合Android官方的开发指导对这几个API做下说明。
checkSelfPermission() 检查是否已经具有了相关权限。任何时候APP都要在执行需要危险权限的操作前去检查是否具有相关权限,即使刚刚执行过这项操作,因为用户很有可能去设置应用中关闭了相关权限。
shouldShowRequestPermissionRationale() 判断是否需要向用户解释,为什么需要这些权限。有时候用户会不理解应用程序为什么需要这些权限。如,相机应用申请摄像头使用权限用户容易理解,但是相机应用申请地理位置使用权限可能会让用户产生疑惑,因为用户很有能不知道相机需要保存每张照片的拍摄地点。这时候我们就需要做适当的解释说明了。这个方法只有在APP请求过某一权限且用户禁止APP使用该权限的时候返回true。在用户授权了权限和禁止权限时勾选了“Don't ask again”选项的情况下都会返回false。Android官方开发指导还提到一点,为避免给用户带来糟糕的用户体验,这里的解释说明应该是异步的,不要阻塞用户的操作。时下很多适配了6.0的APP在这点上处理的都不尽如人意,有的根本没有解释说明,有的是弹出对话框,用户体验都不是很好。下文会给出了一个完美的解决方案。
requestPermissions() 申请相关权限。调用这个方法后会弹出一个系统对话框来向用户申请权限,APP不能自定义这个对话框的内容,这也就增加了上面提到的解释说明的必要性。这里还有一点也需要交代一下。从上面危险权限列表中也可以看出,这些权限都是有分组的。如,READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限就是属于STORAGE组的。分门别类不仅仅是为了方便容易阅读,组内权限在申请上也是有关联的。
在申请组内某个权限时,弹出的系统对话框会显示组名,而不是指明所申请的权限名。如,申请READ_EXTERNAL_STORAGE权限时,系统对话框提示请求“访问sd卡”权限,但不会说明是请求的sd卡读权限;
申请权限时,如果组内有别的权限已经获得了用户授权,系统不再弹出询问对话框,而是自动授权该权限。如,在申请WRITE_EXTERNAL_STORAGE权限时用户已经授权了READ_EXTERNAL_STORAGE权限,系统则会自动授权WRITE_EXTERNAL_STORAGE权限,不再询问用户;
即使有前一条规则存在,在使用每一条权限时都必须(不是应该)调用
requestPermissions()
方法来申请权限。如,在已经获取了READ_EXTERNAL_STORAGE权限的情况下,使用WRITE_EXTERNAL_STORAGE权限时依然需要调用requestPermissions()
方法来申请,否则就会因为权限问题导致写sd卡失败。
注意:危险权限在AndroidManifest.xml文件中也必须申明,否则动态申请会失败。
下面代码是Android官方开发指导中权限申请大致框架。它使用了Android Support Library 中的方法。虽然Android 6.0以后的framework中都有这些方法,但是对于开发者来说使用Android Support Library中的方法更简单,不用检查sdk版本可以兼容低版本。
// Here, thisActivity is the current activityif (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) { // Show an expanation to the user *asynchronously* -- don't blockthis thread waiting for the user's response! // After the user sees the explanation, try again to request the permission. } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(thisActivity, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is anapp-defined int constant. // The callback method gets the result of the request. } }
requestPermissions()申请有结果后会回调onRequestPermissionsResult()
方法,这种方式对于Android开发者一定不会陌生,因为与startActivityForResult()结果回调onActivityResult()方法类似。如下重载onRequestPermissionsResult()
方法即可。
@Overridepublic void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the contacts-related task you need to do. } else { // permission denied, boo! Disable the functionality that depends on this permission. } return; } // other 'case' lines to check for other permissions this app might request } }
由于我们使用了AndroidSupport Library库记得在build.gradle文件中添加依赖。
dependencies { ... compile 'com.android.support:appcompat-v7:23.2.1'}
动态权限申请封装类EasyPermissionsEx
以上就是Android官方开发指导关于动态权限申请的全部内容了。系统的API已经很简洁了,但是用在项目中时依然会出现很多重复代码,按惯例是要封装一下。在github上搜索"Android permissions"会有很多开源库。Google在github上也开源了一个关于动态权限的封装库easypermissions 。看了几个库后,个人觉得easypermissions写的最好,但是也有不足的地方。如,解释为什么需要权限的地方这个库也弹出一个对话框阻塞了用户操作,不符合开发规范、还有用户禁止权限时勾选了“Don't ask again”后依然会弹出对话框请求权限,用户体验不好。所以我在easypermissions库的基础封装了一个EasyPermissionsEx库。说是一个库,其实只有一个300行左右的类,你可以直接拷贝到你的项目中使用。
前文也提到过在解释为什么需要权限或者在用户永久禁止权限后引导用户去设置应用开启权限时,使用对话框会给用户带来不好的体验。EasyPermissionsEx采用的解决方案是使用snackbar来提醒用户。整体效果如下。snackbar是一种轻量级的用户交互,它不会阻塞用户当前的操作,是从底部弹出一个bar来提示用户,类似toast,但是snackbar又有一个button允许用户操作。关于snackbar 更多信息可以跳转到官方文档了解。
Snackbar在Android Design Support Library库中记得在build.gradle文件中添加依赖。
dependencies { ... compile 'com.android.support:design:23.4.0'}
EasyPermissionsEx权限请求逻辑如下。关于EasyPermissionsEx 更多详情可以去github查看wiki,也可以直接看代码,因为它总共也就300行代码。