弓背霞明剑照霜,秋风走马出咸阳。这篇文章主要讲述Android 6.0运行时权限简析及最佳实践相关的知识,希望能为你提供帮助。
1、前言
从android 6.0(API 23)开始,
对系统权限做了很大的改变。在之前用户安装APP前,
只是把APP需要使用的权限列出来给用户告知一下,
APP安装后都可以访问这些权限。从6.0开始,
一些敏感权限,
需要在使用时动态申请,
并且用户可以选择拒绝授权访问这些权限,
已授予过的权限,
用户也可以去APP设置页面去关闭授权。这对用户来说提高了安全性,
可以防止一些应用恶意访问用户数据,
但是对于开发来说,
也增加了不少工作量,
这块不做适配处理的话,
APP在访问权限的时候会容易crash。
2、权限等级和权限组
权限主要分为normal、dangerous、signature和signatureOrSystem四个等级,
常规情况下我们只需要了解前两种,
即正常权限和危险权限。
2.1、正常权限 正常权限涵盖应用需要访问其沙盒外部数据或资源,
但对用户隐私或其他应用操作风险很小的区域。应用声明其需要正常权限,
系统会自动授予该权限。例如设置时区,
只要应用声明过权限,
系统就直接授予应用此权限。下面是截止到API 23的普通权限(需翻墙访问)
普通权限 | 普通权限 |
---|---|
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 |
GET_PACKAGE_SIZE | INSTALL_SHORTCUT |
INTERNET | KILL_BACKGROUND_PROCESSES |
MODIFY_AUDIO_SETTINGS | NFC |
READ_SYNC_SETTINGS | READ_SYNC_STATS |
RECEIVE_BOOT_COMPLETED | REORDER_TASKS |
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | REQUEST_INSTALL_PACKAGES |
SET_ALARM | SET_TIME_ZONE |
SET_WALLPAPER | SET_WALLPAPER_HINTS |
TRANSMIT_IR | UNINSTALL_SHORTCUT |
USE_FINGERPRINT | VIBRATE |
WAKE_LOCK | WRITE_SYNC_SETTINGS |
2.3、权限组 系统根据权限用途又定义了权限组, 每个权限都可属于一个权限组, 每个权限组可以包含多个权限。例如联系人权限组, 包含读取联系人、修改联系人和获取账户三个权限。
* 如果应用申请访问一个危险权限, 而此应用目前没有对应的权限组内的任何权限, 系统会弹窗提示用户要访问的权限组(注意不是权限)。例如无论你申请READ_CONTACTS还是WRITE_CONTACTS, 都是提示应用需要访问联系人信息。
* 如果用户申请访问一个危险权限, 而应用已经授权同权限组的其他权限, 则系统会直接授权, 不会再与用户有交互。例如应用已经请求并授予了READ_CONTACTS权限, 那么当应用申请WRITE_CONTACTS时, 系统会立即授予该权限。下面为危险权限和权限组:
权限组 | 权限 |
---|---|
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 |
关于运行时请求权限, 官网介绍的很清楚, 也有很多其他文章介绍, 这里只是简单罗列一下。
3.1、检查权限 应用每次需要危险权限时, 都要判断应用目前是否有该权限。兼容库中已经做了封装, 只需要通过下面代码即可:
int permissionCheck =
ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果有权限则返回PackageManager.PERMISSION_GRANTED, 否则返回PackageManager。PERMISSION_DENIED。
3.2、请求权限 当应用需要某个权限时, 可以申请获取权限, 这时会有弹出一个系统标准Dialog提示申请权限, 此Diolog不能定制, 用户同意或者拒绝后会通过方法onRequestPermissionsResult()返回结果。当用户拒绝过此权限申请时, 再次申请Dialog上可以勾选不再提示, 这种情况下, 以后再申请权限不会弹Dialog直接返回拒绝。所以一些依赖某些敏感权限的应用, 需要自己去处理, 向用户解释 为什么需要此权限, 说服用户授予权限。请求权限代码如下:
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);
3.3、处理权限请求响应 当用户处理权限请求后, 系统会回调申请权限的Activity的onRequestPermissionsResult()方法, 只需要覆盖此方法, 就能获得返回结果
@
Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// 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;
}
}
}
3.4、考虑使用intent 有很多权限操作可以考虑调用其他应用, 这样的话当前应用就不需要申请权限。例如想要获取相机照相, 可以通过ACTION_IMAGE_CAPTURE唤起相机应用去完成, 相机应用会把照片返回。同样拨打电话、访问联系人, 都可以考虑使用类似方法。相比较其他应用, 这类专门的应用做一些操作更容易让用户接受。
4、 关于国产机6.0以下系统
部分国产厂商定制了系统(例如小米、华为), 在6.0以下系统就可以单独控制权限。但是通常它们处理的不够彻底, 代码中判断是否授权的时候, 返回的是已经授权。而真正去做操作的时候, 却会因为没有权限导致应用crash。例如我曾遇到过的场景:
* 访问联系人, 可以拿到Cursor对象, 但是cursor.moveToFirst()会返回false。
* 访问摄像头时, 获取到的Camera对象为空
所以在低版本手机上, 不要以为拥有系统授权就万事大吉了, 一定要多加条件判读和测试。
5、PermissionGrantor, 一行代码搞定动态权限申请。
上面运行时请求权限中, 我们看到了权限申请依赖于Activity和Fragment, 和startActivityForResult()方法的使用类似, 必须依赖于Activity和Fragment的回调方法。正常情况下还能满足需求, 可是当申请的权限的代码在一个独立的模块中时, 例如我封装了一个UI控件, 控件中某个操作需要申请权限, 或者项目采用了MVVM框架, 需要在一些view类或者model类中申请权限, 这是处理起来就会很麻烦。
我在自己代码中就遇到类似情况, 后面我采用了使用一个单独的Activity来申请权限, 通过回调的方式通知业务层授权结果。感觉使用起来挺方便, 就把它放到maven仓库中了, 项目中通过加入如下依赖即可。
compile '
com.github.dfqin:grantor:2.1.0'
5.1 PermissionGrantor使用 申请权限很简单, 只需要下面一句话即可。
PermissionsUtil.requestPermission(Activity activity, PermissionListener listener,
String[] permissions);
下面是一个申请摄像头的例子:
private void requestCemera() {
if (PermissionsUtil.hasPermission(this, Manifest.permission.CAMERA)) {
//有访问摄像头的权限
} else {
PermissionsUtil.requestPermission(this, new PermissionListener() {
@
Override
public void permissionGranted(@
NonNull String[] permissions) {
//用户授予了访问摄像头的权限
}@
Override
public void permissionDenied(@
NonNull String[] permissions) {
//用户拒绝了访问摄像头的申请
}
}, new String[]{Manifest.permission.CAMERA});
}
}
PermissionsUtil.requestPermission还有两个重载的实现。用户拒绝授权时, 会有一个默认Dialog提示用户开通权限。这个Dialog的内容可以定制, 也可以不显示此Dialog, 详情见Github。有什么问题欢迎讨论交流。
文章图片
文章图片
【Android 6.0运行时权限简析及最佳实践】
文章图片
6、参考
- http://blog.csdn.net/qiujuer/article/details/44195131
- http://2dxgujun.com/post/2015/02/11/Publish-AAR-to-Maven-Central-with-Gradle.html?utm_source= tuicool& utm_medium= referral
- http://www.jianshu.com/p/dbe4d37731e6
推荐阅读
- Androidstudio中导入内部依赖模块总结
- Android之从零开始JNI研发
- android studio环境下创建menu问题(标题栏显示问题)
- Android EventBus技能点梳理
- Android常规布局方式和方法
- Android支付接入(Google In-app-Billing)
- SDK接入之Android Google Play内支付(in-app Billing)接入
- Android自定义属性
- Android四大组件之服务