Android 运行时权限RuntimePermission
引言:运行时权限是版本升级的一个更新点,学起来挺容易的,写一篇笔记感觉真费劲。
时间:2017年04月18日23:45:50
作者:JustDo23
01. 前言
运行时权限是Android 6.0 变更中尤为突出的一点。6以下的手机在安装时候提示权限列表,用户全部同意后才能安装程序;6以后的手机直接安装,在运行过程中动态的向用户申请所需权限,另外用户可以在设置界面对程序的各个权限进行管理。
Android 中的权限可以分为三类:
- 普通权限(Normal Permissions)
- 危险权限(Dangerous Permissions)
- 特殊权限(Special Permissions)
普通权限不涉及用户隐私,在AndroidManifest.xml
中声明即获取;危险权限涉及用户的隐私,在用户授权之后方能使用。另外,Google 对危险权限进行了分组,当某一个组中的某一个权限被授权之后应用同时就获取到了整个组的所有权限。而且,调用 API 申请权限时系统将向用户显示一个标准对话框,该对话框上的提示权限说明是针对整个组的说明,应用无法配置或更改此对话框。
02. 声明权限
在官方文档中提到:为了保护系统的完整性和用户隐私权,Android 在访问受限的沙盒中运行每款应用。如果应用需要使用其沙盒以外的资源或信息,则必须明确请求权限。根据应用请求的权限类型,系统可能会自动授予权限,也可能会要求用户授予权限。可以在应用清单中列出相应的权限,声明应用需要此权限。
声明权限,将<uses-permission>
元素置于顶级<manifest>
元素的子项。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>
03. 检查权限
/**
* Determine whether you have been granted a particular permission.
*
* @param permission The name of the permission being checked.
*/
ContextCompat.checkSelfPermission(@NonNull Context context, @NonNull String permission)
以上为核心 API 检查是否拥有权限,简单封装如下:
/**
* 检查是否具有某权限
*
* @return true, 有权限 false,无权限
*/
private boolean checkPermission(Context context, String permission) {
boolean checkPermission = false;
int permissionCheck = ContextCompat.checkSelfPermission(context, permission);// 检查权限
switch (permissionCheck) {
case PackageManager.PERMISSION_GRANTED:// 有权限
checkPermission = true;
break;
case PackageManager.PERMISSION_DENIED:// 无权限
checkPermission = false;
break;
}
return checkPermission;
}
- 返回的结果是
int
类型 - 常量
PackageManager.PERMISSION_GRANTED
表示有权限 - 常量
PackageManager.PERMISSION_DENIED
表示无权限
// 检查日历权限
ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
04. 请求权限
/**
* 请求指定的权限集合
*
* @param activity The target activity.
* @param permissions The requested permissions. Must me non-null and not empty.
* @param requestCode Application specific request code.
*/
ActivityCompat.requestPermissions(
final @NonNull Activity activity,
final @NonNull String[] permissions,
final @IntRange(from = 0) int requestCode);
- 此方法为异步方法
- 第二参数指定一个权限的字符数组
// 请求一个相机权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 23);
// 同时请求三个权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_CALENDAR, Manifest.permission.READ_SMS}, 32);
05. 请求响应
/**
* Callback for the result from requesting permissions.
*
* @param requestCode The request code.
* @param permissions The requested permissions. Never null.
* @param grantResults The grant results.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case 23:
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
ToastUtil.showShortToast(MainActivity.this, "授权");
} else {
ToastUtil.showShortToast(MainActivity.this, "拒绝");
}
}
break;
case 32:
break;
}
}
- 第二个参数是请求的权限数组
- 第三个参数是与请求数组对应的授权结果数组
06. 提供解释
/**
* Gets whether you should show UI with rationale for requesting a permission.
*
* @param activity The target activity.
* @param permission A permission your app wants to request.
* @return Whether you can show permission rationale UI.
*/
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission) {
if (Build.VERSION.SDK_INT >= 23) {
return ActivityCompatApi23.shouldShowRequestPermissionRationale(activity, permission);
}
return false;// 低版本直接返回 false
}
- 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回
true
。 - 如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回
false
。 - 如果设备规范禁止应用具有该权限,此方法也会返回
false
。
这部分的内容Android M 权限最佳实践这篇文章写得很好。
07. 官方例子
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this 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_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
}
08. 兼容问题
首先明确一下在什么情况下需要使用运行时权限动态申请:
- 危险权限
- Android 版本 >= 6.0
- targetSdkVersion >= 23
在这三个条件同时具备的情况下必须使用运行时权限机制。当targetSdkVersion < 23
的时候不用
调用运行时权限机制,当targetSdkVersion >= 23
的时候必须
调用运行时权限机制以确保程序不会出现崩溃。另外,运行时权限机制的相关 API 都是在support.v4
包下,说明做了低版本兼容,相关 API 运行在低版本时候均有默认的返回值。在此基础上兼容适配问题可以考虑从两个因素Android 版本
和targetSdkVersion
入手,用一张表来简单分析兼容适配问题:
targetSdkVersion | Android 版本 | 兼容适配 |
---|---|---|
targetSdkVersion < 23 | SDK < 23 | 安装时授权,正常使用 |
targetSdkVersion < 23 | SDK >= 23 | 直接安装并授权,用户管理权限,可能崩溃 |
targetSdkVersion >= 23 | SDK < 23 | 安装时授权,运行时权限 API 有默认返回值,正常使用 |
targetSdkVersion >= 23 | SDK >= 23 | 直接安装,运行时授权,用户管理权限,正常使用 |
从表格中看出如果应用targetSdkVersion < 23
运行在Android 6.0
的手机上,由于用户可以自主管理权限,取消某些授权,便会引起程序崩溃。因此,尽快提升目标版本同时添加运行时权限判断申请 API 是很有必要的。
注意:对于以 Android 6.0 或更高版本为目标平台的应用,请务必在运行时检查和请求权限。即使不以 Android 6.0 为目标平台,也应该在新权限模式下测试应用。
09. 其他小点
- 如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机,它不能假设自己今天仍具有该权限。
- 用户只需要为每个权限组授予一次权限。当请求已经被授予的权限时,系统会调用您的
onRequestPermissionsResult()
回调方法,并传递PERMISSION_GRANTED
。 - 当系统要求用户授予权限时,用户可以选择指示系统不再要求提供该权限。这种情况下,无论应用在什么时候使用
requestPermissions()
再次要求该权限,系统都会立即拒绝此请求。系统会调用您的onRequestPermissionsResult()
回调方法,并传递PERMISSION_DENIED
。
10. 奇思怪想
- 同时申请多条相同权限时,只有一个权限提示。
- 同时申请同组的多条权限时,只有一个权限提示。
- 在
onRequestPermissionsResult
方法中的权限字符数组与requestPermissions
方法中的相同。 - 先检查权限再申请权限。原因是第一次申请权限勾选允许,再次申请权限勾选拒绝,程序崩溃。同时申请多个权限,有的拒绝有的允许,再次申请时应该先检查并剔除已经允许的权限。注意:崩溃出现在6.0的手机上,在7.0上回自动返回授权或者自动剔除。
11. 推荐文章
有几篇非常不错的文章值得阅读学习: