Android内部分享[8]——Android系统的应用程序权限申请

Android 中的权限概述

Android 中需要申请权限的目的是保护用户的隐私。如果你的应用程序需要访问用户的敏感数据,必须申请对应的访问权限(如联系人和短信) ,还有一些系统功能的使用也需要申请权限(如摄像头和互联网)。根据功能和安全等级的不同,Android 系统可能会自动授予权限,也可能提示用户确认并同意权限请求。

Android 安全架构设计的一个核心点就是默认情况下没有任何应用程序有权执行任何可能对其他应用程序、操作系统或用户造成不利影响的操作。这包括读取或写入用户的私有数据 (如联系人或电子邮件)、读取或写入另一个应用程序的文件、执行网络访问、保持设备处于清醒状态等等。

接下来我们来看一下 Android 权限的工作原理,包括:权限如何呈现给用户、安装时和运行时权限请求之间的区别、如何执行权限、权限类型及其权限组。

申请Android应用程序权限

应用程序必须通过在应用程序清单(app manifest)中包含 <uses-permission> 标记来公开它需要的权限。例如,需要发送 SMS 消息的应用程序清单中会有下面这一行权限声明:

1
2
3
4
5
6
7
8
9
<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>

如果您的应用程序在其清单中列出了普通权限(即不会对用户隐私或设备操作造成太大风险的权限),系统会自动将这些普通权限授予您的应用程序。如果您的应用程序在其清单中列出了危险的权限(即可能影响用户隐私或设备正常运行的权限),例如上面的 SEND_SMS 权限,用户必须明确同意授予这些权限。

Android应用程序权限的级别

权限分为几个保护级别将权限区分为两个大类,在安装时自动授权还是需要在运行时让用户同意授予这些权限。

普通级别权限

普通级别权限包括应用程序需要访问应用程序沙箱之外的数据或资源的区域,但这些区域对用户的隐私或其他应用程序的操作几乎没有风险。例如,设置时区的权限是正常的权限。如果一个应用程序在其清单中声明它需要一个普通级别的权限,系统会在安装时自动授予该权限。授予普通权限的时候系统不会弹框提示用户,用户也不能撤销这些被授予的权限。

危险级别权限

危险权限包括应用程序需要涉及用户私人信息的数据或资源的区域,或者可能影响用户存储的数据或其他应用程序的操作。例如,读取用户联系人的能力是一种危险的权限。如果一个应用程序声明它需要一个危险的权限,用户必须显式地授予该应用程序该权限。在用户批准该权限之前,应用程序不能提供依赖于该权限的功能。要使用危险的权限,应用程序必须在运行时提示用户授予权限。Android 要求用户授予危险权限的方式取决于用户设备上运行的 Android 版本,以及应用程序所针对的系统版本。

特殊访问权限

有几个权限的申请和普通权限和危险权限不一样。例如系统警告窗口和写设置特别敏感,所以大多数应用程序不应该使用它们,如果应用程序需要这些权限之一,它必须在清单中声明该权限,并发送意图(Intent)请求用户的授权。系统通过向用户显示详细的管理屏幕来响应该意图(Intent)。

Android 6.0 之后运行时权限请求

如果设备运行 Android 6.0 (API level 23) 或更高版本,且应用程序的 targetSdkVersion 为 23 或更高版本,则在安装时不会通知用户任何应用程序权限。您的应用程序必须要求用户在运行时授予危险的权限。当您的应用程序请求权限时,用户将看到一个系统对话框(如下图所示),告诉用户您的应用程序试图访问哪个权限组。对话框包含一个拒绝(Deny)和允许(Allow)按钮。

如果用户拒绝了权限请求,那么下一次您的应用程序请求权限时,对话框将包含一个复选框,选中该复选框后,将有个选项是用户不希望再次收到请求权限的提示(如下图所示)。

初始权限对话框(左)和二级权限请求,并带有 Deny 和 Allow 按钮的请求的选项(右) 初始权限对话框(左)和二级权限请求,并带有 Deny 和 Allow 按钮的请求的选项(右)

如果用户选中“永不再问”(Never ask again)框并单击“拒绝”(Deny),那么如果稍后尝试请求相同的权限,系统将不再提示用户。

即使用户授予您的应用程序它所请求的权限,您也不能总是依赖它。用户还可以在系统设置中逐个启用和禁用权限。您应该始终在运行时检查和请求权限,以防止运行时错误 (SecurityException)。

运行时权限实践

在所有版本的 Android 上 ,要声明应用程序需要权限,请在应用程序清单中放置一个 <use -permission> 元素,作为顶层 <manifest> 元素的子元素。例如,需要访问 internet 的应用程序在清单中有这一行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.snazzyapp">

    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- other permissions go here -->

    <application ...>
        ...
    </application>
</manifest>

要检查是否有权限,请调用 ContextCompat.checkSelfPermission() 方法。例如,这段代码展示了如何检查活动是否具有写入日历的权限。

1
2
3
4
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR)
        != PackageManager.PERMISSION_GRANTED) {
    // Permission is not granted
}

如果应用程序具有该权限,该方法将返回已授予的权限,应用程序可以继续操作。如果应用程序没有权限,该方法返回被拒绝的权限,应用程序必须显式地请求用户的权限。

当您的应用程序收到 checkSelfPermission() 拒绝的权限时,您需要提示用户获得该权限。Android 提供了几种请求权限的方法,比如 requestPermissions(),如下面的代码片段所示。调用这些方法会弹出一个标准的 Android 对话框,您无法自定义该对话框。

我们应该在 ``requestPermissions()做一步判断,判断用户是否已经完全拒绝了这个权限(也就是上面所说的永不再询问),通过shouldShowRequestPermissionRationale()` 方法判断,如果返回 true 则它可以帮助你再一次的提醒用户,你需要这个权限,也是你最后翻身的方法了。但是在实际开发中,不得不说的一个坑,由于国内第三方 ROM 对系统改造的太严重,比如小米,亲测有些机型的这个方法是不起作用的,永远的是返回 false,这个时候该怎么办,就要另外想解决方案了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
if (ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
    // 未获得权限
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {
        //说明用户明确拒绝了某个权限,我们应该在这里做出提示给用户,说清楚此权限的用户
    } else {
        // 请求申请权限
        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    }
} else {
    // 权限已获得
}

当我们权限被申请授予的时候我们可以第一时间通过重写 onRequestPermissionsResult 函数获得权限申请结果,在这里处理获取后的逻辑代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Override
public void onRequestPermissionsResult(int requestCode,
        String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // 如果请求被取消,则结果数组为空。
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 权限申请成功
            } else {
                // 权限申请失败
            }
            return;
        }
    }
}

本文出自水寒的个人博客,转载请说明出处:https://dp2px.com

Android 5.1.1 及其以下安装时权限申请

如果设备运行的是 Android 5.1.1 (API 级别 22)或更低,或者应用程序的 targetSdkVersion 在运行任何版本的 Android 时都是 22 或更低,系统会自动要求用户在安装时为应用程序授予所有危险的权限(参见下图)。

安装时权限申请对话框 安装时权限申请对话框

如果用户单击 Accept,应用程序请求的所有权限都将被授予。如果用户拒绝权限请求,系统将取消应用程序的安装。如果应用程序更新包含对附加权限的需要,则提示用户在更新应用程序之前接受这些新权限。

可选的硬件功能权限

访问某些硬件功能(如蓝牙或摄像头)需要获得应用程序许可。然而,并非所有 Android 设备都具有这些硬件特性。因此,如果您的应用程序请求相机许可,那么您还需要在清单中包含 <uses-feature> 标记,以声明是否实际需要该功能。例如:

1
<uses-feature android:name="android.hardware.camera" android:required="false" />

如果你声明的 android:required="false" 那么谷歌播放允许对不具有该功能的设备安装在您的应用程序。 然后,您必须检查当前的设备通过调用 PackageManager.hasSystemFeature(具有在运行时的功能),并优雅地禁用该功能,如果它是不可用的。

如果您不提供 <uses-feature> 标记,那么当谷歌 Play 看到您的应用程序请求相应的权限时,它假定您的应用程序需要该特性。因此,它会从没有该功能的设备中过滤您的应用程序,就像您在 <uses-feature> 标签中声明 android:required="true" 一样。

Android中系统权限的分组

权限被组织成与设备功能或特性相关的组。在该系统中,权限请求在组级处理,单个权限组对应于 app 清单中的多个权限声明。例如,SMS 组包括读 SMS 和接收 SMS 声明。以这种方式分组权限使用户能够做出更有意义、更明智的选择,而不会被复杂的技术权限请求所淹没。

所有危险的 Android 权限都属于权限组。无论保护级别如何,任何权限都可以属于权限组。然而,权限组仅在权限是危险的情况下才会影响用户体验。

如果设备运行 Android 6.0 (API 级别 23),且应用程序的 targetSdkVersion 为 23 或更高,则当应用程序请求危险权限时,将应用以下系统行为:

  • 如果应用程序当前在权限组中没有任何权限,系统将向描述应用程序希望访问的权限组的用户显示权限请求对话框。对话框不描述该组中的特定权限。例如,如果一个应用程序请求 READ_CONTACTS 权限,系统对话框只说应用程序需要访问设备的联系人。如果用户给予批准,系统只会给应用程序它所请求的权限。
  • 如果应用程序已经在同一权限组中被授予了另一个危险权限,系统将立即授予该权限,而不与用户进行任何交互。例如,如果一个应用程序之前请求并被授予读联系人权限,然后它请求写联系人,系统立即授予该权限,而不向用户显示权限对话框。

如果设备运行 Android 5.1 (API 级别 22)或更低,或者应用程序的 targetSdkVersion 是 22 或更低,系统要求用户在安装时授予权限。同样,系统只是告诉用户应用程序需要哪些权限组,而不是单个权限。例如,当应用程序请求读取联系人时,安装对话框会列出联系人组。当用户接受时,只向应用程序授予 READ_CONTACTS 权限。

和系统权限相关的 adb 命令

我们可以是 adb 命令查看整个系统的所有可用权限:

1
2
3
$ adb shell pm list permissions -s
All Permissions:

也可以使用 adb 命令强制授予某应用的所有权限:

1
$ adb shell install -g MyApp.apk

关于 Android 6.0 和 Android 7.0 权限问题,在这之前我还写过一篇文章,你可以作为本文的另一篇参考文章《Android 6.0/7.0权限问题》