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

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)的正常权限如下:

  1. ACCESS_LOCATION_EXTRA_COMMANDS
  2. ACCESS_NETWORK_STATE
  3. ACCESS_NOTIFICATION_POLICY
  4. ACCESS_WIFI_STATE
  5. BLUETOOTH
  6. BLUETOOTH_ADMIN
  7. BROADCAST_STICKY
  8. CHANGE_NETWORK_STATE
  9. CHANGE_WIFI_MULTICAST_STATE
  10. CHANGE_WIFI_STATE
  11. DISABLE_KEYGUARD
  12. EXPAND_STATUS_BAR
  13. GET_PACKAGE_SIZE
  14. INSTALL_SHORTCUT
  15. INTERNET
  16. KILL_BACKGROUND_PROCESSES
  17. MANAGE_OWN_CALLS
  18. MODIFY_AUDIO_SETTINGS
  19. NFC
  20. READ_SYNC_SETTINGS
  21. READ_SYNC_STATS
  22. RECEIVE_BOOT_COMPLETED
  23. REORDER_TASKS
  24. REQUEST_COMPANION_RUN_IN_BACKGROUND
  25. REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
  26. REQUEST_DELETE_PACKAGES
  27. REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
  28. REQUEST_INSTALL_PACKAGES
  29. SET_ALARM
  30. SET_WALLPAPER
  31. SET_WALLPAPER_HINTS
  32. TRANSMIT_IR
  33. USE_FINGERPRINT
  34. VIBRATE
  35. WAKE_LOCK
  36. 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_WINDOWWRITE_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_WINDOWWRITE_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消息的应用程序在清单中有这一行:

<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>

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

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中注册
  • 需写动态申请代码
  • 申请时出现如下提示

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<String> deniedPermissions = findDeniedPermissions(activity, permissions);
        if (!deniedPermissions.isEmpty()) {
            activity.requestPermissions(permissions, requestCode);
        }
    }
    
    /**
     * @return 未获得的权限
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private static List<String> findDeniedPermissions(Activity activity, String... permission) {
        List<String> 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);
    }

参考:

转载于:https://my.oschina.net/lichuangnk/blog/1826501

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值