Android 运行时请求权限大家都并不陌生,网上类似的 Sample 铺天盖地。为什么我还要针对 Android 运行时请求权限去做文章呢?那就是适配 Android O !
在 Android 官方文档中 https://developer.android.google.cn/preview/behavior-changes.html
对 Android O 的行为变更中的运行时请求权限做了修改。官方文档中强调:
说白了就是对权限申请更加严格。针对这一变更,作为开发者既要兼顾 Android M - Android N 的运行时请求权限,也要适配 Android O 的运行时请求权限。
好的,跟着我的思路开始我们的运行时请求权限解决方案。
先看效果图:
① 在没授予权限之前,所有需要的权限都是未选中状态。
1.png
② Android 运行时请求权限。
2.gif
③ 权限授予之后,所有需要的权限都是选中状态。
3.png
看过示例图之后,我们先来了解一下哪些权限需要在运行时请求。
根据官方 API 文档 https://developer.android.google.cn/guide/topics/security/permissions.html 中所描述的内容,我们可以归纳出以下几点需要注意的地方:
-
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。
-
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。
-
可能在程序运行期间的多个位置实施特定权限:
① 在调用系统时,防止应用执行某些功能。
② 在启动 Activity 时,防止应用启动其他应用的 Activity。
③ 在发送和接收广播时,控制谁可以接收您的广播,谁可以向您发送广播。
④ 在访问和操作内容提供程序时。
⑤ 绑定至服务或启动服务。 -
正常权限和危险权限
① 正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。
② 危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。 -
危险权限和权限组
分成 9 大类:
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
了解了哪些权限属于危险权限之后,跟随我一起进行 Android Runtime Permissions 解决方案。
(这里以 CALENDAR 为例,其他大同小异,获取完整代码请拖拽至文章末尾处。)
- 定义请求权限
private static final int REQUEST_CALENDAR = 0;
- 考虑到 Android O 之后的权限请求,故将同一类的都放在一个数组里进行处理
private static String[] PERMISSION_CALENDAR = {
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
};
- 做权限请求的回调
public void showCalendar(View viewShowCalendar) {
Log.i(TAG, "Show calendar button pressed. Checking permissions.");
// Verify that all required contact permissions have been granted.
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
// Calendar permissions have not been granted.
Log.i(TAG, "Calendar permissions has not been granted. Requesting permissions.");
requestCalendarPermissions();
} else {
// Calendar permissions have been granted. Show the contacts fragment.
Log.i(TAG,
"Calendar permissions have already been granted. Displaying calendar details.");
showCalendarDetails();
}
}
这里就要做分支处理:
① 没有给予请求所需的权限,就要发出权限请求 requestCalendarPermissions()
② 如果已授予权限,那么就执行你想要执行的动作 showCalendarDetails()
- 发出权限请求
private void requestCalendarPermissions() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CALENDAR)
|| ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_CALENDAR)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example, if the request has been denied previously.
Log.i(TAG, "Displaying calendar permission rationale to provide additional context.");
// Display a SnackBar with an explanation and a button to trigger the request.
Snackbar.make(mLayout, R.string.permission_calendar_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat
.requestPermissions(MainActivity.this, PERMISSION_CALENDAR,
REQUEST_CALENDAR);
}
})
.show();
} else {
// Calendar permissions have not been granted yet. Request them directly.
ActivityCompat.requestPermissions(this, PERMISSION_CALENDAR, REQUEST_CALENDAR);
}
}
- 当权限已被授于,自定义执行动作
private void showCalendarDetails() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_fragment, CalendarFragment.newInstance())
.addToBackStack("calendar")
.commit();
}
✱✱✱ 重中之重 ✱✱✱ 当权限请求完成之后回调。重写 onRequestPermissionsResult()
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CALENDAR) {
Log.i(TAG, "Received response for calendar permissions request.");
// We have requested multiple permissions for calendar, so all of them need to be
// checked.
if (PermissionUtil.verifyPermissions(grantResults)) {
// All required permissions have been granted, display contacts fragment.
Snackbar.make(mLayout, R.string.permission_available_calendar,
Snackbar.LENGTH_SHORT)
.show();
} else {
Log.i(TAG, "Calendar permissions were not granted.");
Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT)
.show();
}
}
...
}
</br>
最后在 AndroidManifest 里面申请需要的权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.haocent.android.androidruntimepermissions">
<uses-permission-sdk-23 android:name="android.permission.READ_CALENDAR" />
<uses-permission-sdk-23 android:name="android.permission.WRITE_CALENDAR" />
...
</manifest>
项目代码已上传至 GitHub https://github.com/cnwutianhao/AndroidRuntimePermissions
欢迎 Star 、Fork 。如有遗漏或错误请指摘。
项目已在以下机器上进行完整测试
Nexus 5 Android 6.0.1 (真机)
Nexus 5x Android O (模拟器)
网友评论