Android权限详解(含6.0动态申请)

本文目的:使读者深入理解及快速使用(代码复制即可用)

Android权限官网

本文概念定义内容主要翻译自Android官网

一、权限概述 Permissions Overview

权限的目的是保护Android用户的隐私。Android应用程序必须请求允许访问敏感用户数据(如联系人和SMS),以及某些系统特征(如摄像头和Internet)。根据该特征,系统可以自动授予许可,或者提示用户批准请求。

The purpose of a permission is to protect the privacy of an Android user. Android apps must request permission to access sensitive user data (such as contacts and SMS), as well as certain system features (such as camera and internet). Depending on the feature, the system might grant the permission automatically or might prompt the user to approve the request.

二、权限等级 Protection levels(此处非直译)

权限分为几个保护级别。保护级别影响是否需要运行时权限请求。

Permissions are divided into several protection levels. The protection level affects whether runtime permission requests are required.

有三种保护级别影响第三方应用程序: 正常、签名和危险权限。

There are three protection levels that affect third-party apps: normal, signature, and dangerous permissions.

所有的权限都可以在Manifest.permission中找到。

正常权限 Normal permissions

正常权限覆盖了应用程序需要访问应用程序沙箱外的数据或资源的区域,但对用户的隐私或其他应用程序的操作几乎没有风险。例如,设置时区的权限是正常权限。

如果应用程序在其清单中声明需要正常权限, 则系统会在安装时自动授予应用程序该权限。系统不提示用户授予正常权限, 用户不能撤消这些权限。

Normal permissions cover areas where your app needs to access data or resources outside the app's sandbox, but where there's very little risk to the user's privacy or the operation of other apps. For example, permission to set the time zone is a normal permission.

If an app declares in its manifest that it needs a normal permission, the system automatically grants the app that permission at install time. The system doesn't prompt the user to grant normal permissions, and users cannot revoke these permissions.

Android 8.1 (API level 27)的正常权限如下:

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

MANAGE_OWN_CALLS

MODIFY_AUDIO_SETTINGS

NFC

READ_SYNC_SETTINGS

READ_SYNC_STATS

RECEIVE_BOOT_COMPLETED

REORDER_TASKS

REQUEST_COMPANION_RUN_IN_BACKGROUND

REQUEST_COMPANION_USE_DATA_IN_BACKGROUND

REQUEST_DELETE_PACKAGES

REQUEST_IGNORE_BATTERY_OPTIMIZATIONS

REQUEST_INSTALL_PACKAGES

SET_ALARM

SET_WALLPAPER

SET_WALLPAPER_HINTS

TRANSMIT_IR

USE_FINGERPRINT

VIBRATE

WAKE_LOCK

WRITE_SYNC_SETTINGS

危险权限 Dangerous permissions

危险权限覆盖应用程序需要数据或资源的区域,这些数据或资源涉及用户的私有信息,或者可能影响用户的存储数据或其他应用程序的操作。例如,读取用户联系人的能力是危险的许可。如果一个应用程序声明它需要一个危险的权限,用户必须明确地授予应用程序的权限。在用户批准权限之前,应用程序不能提供依赖于该权限的功能。

Dangerous permissions cover areas where the app wants data or resources that involve the user's private information, or could potentially affect the user's stored data or the operation of other apps. For example, the ability to read the user's contacts is a dangerous permission. If an app declares that it needs a dangerous permission, the user has to explicitly grant the permission to the app. Until the user approves the permission, your app cannot provide functionality that depends on that permission.

危险权限 和 权限组(权限组)

group:android.permission-group.CONTACTS

permission:android.permission.WRITE_CONTACTS

permission:android.permission.GET_ACCOUNTS

permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE

permission:android.permission.READ_CALL_LOG

permission:android.permission.READ_PHONE_STATE

permission:android.permission.CALL_PHONE

permission:android.permission.WRITE_CALL_LOG

permission:android.permission.USE_SIP

permission:android.permission.PROCESS_OUTGOING_CALLS

permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR

permission:android.permission.READ_CALENDAR

permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA

permission:android.permission.CAMERA

group:android.permission-group.SENSORS

permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION

permission:android.permission.ACCESS_FINE_LOCATION

permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE

permission:android.permission.READ_EXTERNAL_STORAGE

permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE

permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS

permission:android.permission.READ_SMS

permission:android.permission.RECEIVE_WAP_PUSH

permission:android.permission.RECEIVE_MMS

permission:android.permission.RECEIVE_SMS

permission:android.permission.SEND_SMS

permission:android.permission.READ_CELL_BROADCASTS

特殊权限 Special permissions

有一些权限不像正常和危险权限。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS设置特别敏感,因此大多数应用程序不应该使用它们。如果应用程序需要这些权限中的一个,则必须声明清单中的权限,并发送请求用户授权的Intent。系统通过向用户显示详细的管理屏幕来响应Intent。

There are a couple of permissions that don't behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most apps should not use them. If an app needs one of these permissions, it must declare the permission in the manifest, and send an intent requesting the user's authorization. The system responds to the intent by showing a detailed management screen to the user.

SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 在6.0前为安装时授权,6.0之后为特殊权限

以WRITE_SETTINGS讲解Special permissions 在6.0之后的申请

WRITE_SETTINGS:Allows an application to read or write the system settings.

Note: If the app targets API level 23 or higher, the app user must explicitly grant this permission to the app through a permission management screen. The app requests the user's approval by sending an intent with action Settings.ACTION_MANAGE_WRITE_SETTINGS. The app can check whether it has this authorization by calling Settings.System.canWrite().

示例代码:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

if (!Settings.System.canWrite(this)) {

Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + getPackageName()));

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(intent);

} else {

//有了权限,你要做什么呢?具体的动作

}

}

签名权限 Signature permissions

使用相同证书app之间权限,不常用,此处只做简介。【官网】

注:SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 属于系统Signature permissions

系统在安装时授予这些应用程序权限,但只有当试图使用权限的应用程序与定义权限的应用程序签署相同的证书时才可用。

The system grants these app permissions at install time, but only when the app that attempts to use a permission is signed by the same certificate as the app that defines the permission.

三、权限组 Permission groups

权限被组织成与设备的功能或特性相关的组。在这个系统中,权限请求在组级别上被处理,单个权限组对应于应用程序清单中的多个权限声明。例如,SMS组既包括READ_SMS又包括RECEIVE_SMS声明。以这种方式分组权限使得用户能够做出更有意义和更明智的选择,而不是被复杂专业的权限请求搞成SB。

Permissions are organized into groups related to a device's capabilities or features. Under this system, permission requests are handled at the group level and a single permission group corresponds to several permission declarations in the app manifest. For example, the SMS group includes both the READ_SMS and the RECEIVE_SMS declarations. Grouping permissions in this way enables the user to make more meaningful and informed choices, without being overwhelmed by complex and technical permission requests.

所有危险的Android权限都属于权限组。任何权限都可以属于权限组,而不考虑保护级别。但是,只有当权限是危险权限时,权限组才会影响用户体验。

All dangerous Android permissions belong to permission groups. Any permission can belong to a permission group regardless of protection level. However, a permission's group only affects the user experience if the permission is dangerous.

四、权限实施 Permission enforcement

不常用:此处只做简介【官网】

类别:

Activity permission enforcement

Service permission enforcement

Service permission enforcement

Content Provider permission enforcement

URI permissions

Other permission enforcement

四大组件permission enforcement简介:

在manifest组件标签中使用android:permission后,只有拥有权限的调用者才能调用,无权限一般抛出SecurityException

URI permissions(不常用)

当与内容提供者一起使用时,到目前为止所描述的标准权限系统通常是不够的。内容提供者可能希望用读写权限来保护自己,而它的直接客户也需要将特定的URI应用到其他应用程序上进行操作。

The standard permission system described so far is often not sufficient when used with content providers. A content provider may want to protect itself with read and write permissions, while its direct clients also need to hand specific URIs to other apps for them to operate on.

一个典型的例子是电子邮件应用程序中的附件。电子邮件的访问应该受到权限保护,因为这是敏感的用户数据。然而,如果向图像查看器提供了图像附件的URI,则该图像查看器不再具有打开附件的权限,因为它没有理由持有访问所有电子邮件的权限。

A typical example is attachments in a email app. Access to the emails should be protected by permissions, since this is sensitive user data. However, if a URI to an image attachment is given to an image viewer, that image viewer no longer has permission to open the attachment since it has no reason to hold a permission to access all email.

这个问题的解决方案是通过URI权限:当启动一个activity或将结果返回到一个activity时,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这授权接收的activity访问intent中的特定数据URI,而不管它是否具有访问内容提供者中与intent对应的数据的任何权限。

The solution to this problem is per-URI permissions: when starting an activity or returning a result to an activity, the caller can set Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION. This grants the receiving activity permission access the specific data URI in the intent, regardless of whether it has any permission to access data in the content provider corresponding to the intent.

Other permission enforcement

Arbitrarily fine-grained permissions can be enforced at any call into a service. This is accomplished with the Context.checkCallingPermission() method. Call with a desired permission string and it returns an integer indicating whether that permission has been granted to the current calling process. Note that this can only be used when you are executing a call coming in from another process, usually through an IDL interface published from a service or in some other way given to another process.

五、权限获取 Permission approval

应用程序必须通过在应用程序清单中包含标签来公开其所需的权限。例如,一个需要发送SMS消息的应用程序在清单中有这一行:

package="com.example.snazzyapp">

...

如果你的应用程序在它的清单中列出了正常的权限(也就是说,权限不会对用户的隐私或设备的操作造成太大的风险),系统会自动将这些权限授予你的应用。

If your app lists normal permissions in its manifest (that is, permissions that don't pose much risk to the user's privacy or the device's operation), the system automatically grants those permissions to your app.

如果您的应用程序列出了其清单中的危险权限 (即可能影响用户隐私或设备正常操作的权限), 例如上面的 SEND_SMS 权限, 则用户必须明确同意授予这些权限。

If your app lists dangerous permissions in its manifest (that is, permissions that could potentially affect the user's privacy or the device's normal operation), such as the SEND_SMS permission above, the user must explicitly agree to grant those permissions.

Android 6.0 之前获取权限特点(设备运行于6.0之下 或者 targetSdkVersion 低于23)

系统要求用户在安装时授予权限。系统只告诉用户应用程序需要什么权限组,而不是个人权限。例如,当应用程序请求READ_CONTACTS时,安装对话框列出联系人组。当用户接受时,只有READ_CONTACTS权限被授予应用程序。

Android 6.0 (API level 23)获取权限特点(设备运行于6.0之上,并且 targetSdkVersion 高于23)

如果应用程序当前没有权限组中的任何权限,系统将向用户显示权限请求对话框,向用户描述应用程序想要访问的权限组。该对话框不描述该组中的特定权限。例如,如果一个应用程序请求READ_CONTACTS权限,系统对话框只是说应用程序需要访问设备的联系人。如果用户同意,系统只给应用程序请求的权限。

如果应用程序已经获取同一权限组中另一个危险权限,系统立即授予权限而不与用户进行任何交互。例如,如果一个应用程序先前请求并获得了READ_CONTACTS权限,然后它请求READ_CONTACTS,系统立即授予该权限,而不向用户显示权限对话框。

【注意】:权限申请不要基于用户组(含6.0前后所有版本)

Caution: Future versions of the Android SDK might move a particular permission from one group to another. Therefore, don't base your app's logic on the structure of these permission groups.

六、权限申请过程

Android 6.0 之前

【申请过程】

需在manifest中注册

在安装时会进行提示,不接受会取消安装应用

If the user clicks Accept, all permissions the app requests are granted. If the user denies the permissions request, the system cancels the installation of the app.

Android 6.0 之后

【申请过程】

需在manifest中注册

需写动态申请代码

申请时出现如下提示

Android mainifest 网络授权 android获取网络权限_应用程序

Initial permission dialog (left) and secondary permission request with option to turn off further requests (right)

【第一次申请】

第一次申请时,弹左侧弹窗,选择allow后即永久获得权限。

若选择deny,本次权限申请被拒绝,下次申请此权限弹右侧弹窗。

【第一次选deny后再次申请】

出现右侧弹窗。Never ask again决定本次是单次授权还是永久授权。

七、Android 6.0动态权限申请代码

相关API

检查权限android.content.ContextWrapper#checkSelfPermission

申请授权android.app.Activity#requestPermissions

处理权限申请回调android.app.Activity#onRequestPermissionsResult

是否应该提示android.app.Activity#shouldShowRequestPermissionRationale

【Activity#shouldShowRequestPermissionRationale】

注:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。

当用户选择deny并选择Never ask again,我们此后申请权限时都会失败,因此需对用户进行提示。

在处理权限申请回调android.app.Activity#onRequestPermissionsResult时,结合shouldShowRequestPermissionRationale返回值特性,可判断是否为此种情形。

代码如下:

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

for (int index = 0; index < permissions.length; index++) {

if (grantResults[index] != PackageManager.PERMISSION_GRANTED

&& !activity.shouldShowRequestPermissionRationale(permissions[index])) {

//用户之前拒绝,并勾选不再提示时,在此引导用户去设置页设置权限

}

}

}

申请权限

/**

* 申请权限

*/

@RequiresApi(api = Build.VERSION_CODES.M)

public static void requestPermissions(@NonNull Activity activity, @NonNull String[] permissions, int requestCode) {

if (!needRequestPermission(activity, permissions)) {

return;

}

List deniedPermissions = findDeniedPermissions(activity, permissions);

if (!deniedPermissions.isEmpty()) {

activity.requestPermissions(permissions, requestCode);

}

}

/**

* @return 未获得的权限

*/

@RequiresApi(api = Build.VERSION_CODES.M)

private static List findDeniedPermissions(Activity activity, String... permission) {

List denyPermissions = new ArrayList<>();

for (String value : permission) {

if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {

denyPermissions.add(value);

}

}

return denyPermissions;

}

/**

* Android系统6.0 往上

*/

private static boolean versionOverM() {

return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;

}

/**

* @return true: 需要动态获取的权限未全部获得

*/

@RequiresApi(Build.VERSION_CODES.M)

private static boolean permissionNotGranted(Activity activity, String... permission) {

for (String value : permission) {

if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {

return true;

}

}

return false;

}

/**

* @return true: 所需要的权限未全部已获取,需申请权限

*/

private static boolean needRequestPermission(Activity activity, String... permission) {

return versionOverM() && permissionNotGranted(activity, permission);

}

处理申请结果回调

@RequiresApi(api = Build.VERSION_CODES.M)

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

PermissionUtils.dispatchPermissionResult(this, requestCode, permissions, grantResults);

switch (requestCode) {

//do what you need

}

}

/**

* 申请权限后,是否所有的权限都申请成功

*

* @return true:所有权限申请成功

*/

public static boolean isAllPermissionsGranted(int... permissions) {

if (permissions == null) {

return true;

}

for (int p : permissions) {

if (p != PackageManager.PERMISSION_GRANTED) {

return false;

}

}

return true;

}

/**

* 权限申请统一处理信息

*/

@RequiresApi(api = Build.VERSION_CODES.M)

public static void dispatchPermissionResult(@NonNull Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

if (permissions.length == 0 || grantResults.length == 0) {

return;

}

for (int index = 0; index < permissions.length; index++) {

if (grantResults[index] != PackageManager.PERMISSION_GRANTED

&& !activity.shouldShowRequestPermissionRationale(permissions[index])) {

//用户之前拒绝,并勾选不再提示时,在此引导用户去设置页设置权限

//jumpToSettingPage(activity);

}

}

}

/**

* 帮跳转到该应用的设置界面,让用户手动授权

*/

private static void jumpToSettingPage(@NonNull Activity activity) {

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);

Uri uri = Uri.fromParts("package", activity.getPackageName(), null);

intent.setData(uri);

activity.startActivity(intent);

}