Android 运行时权限官方指南

作者: MrTrying | 来源:发表于2017-11-30 08:21 被阅读610次
RuntimePermission

文章主要基于API文档完成,并未对Android权限写的很详尽,更详细的Android权限相关内容还请移步官方文档。

一、前言

运行时权限是Android M (即6.0版本)引入了新的权限模式,用户能在运行时直接管理app权限。改善用户对权限的控制和可见性,同时为开发者简化了app安装和自动更新过程。用户可以在app安装之后单独授予或撤销权限。

二、系统权限

Android是一个权限分离的操作系统,每个应用程序都以不同的系统标识(Linux用户标识和组标识)运行。系统的一部分也被分成不同的身份。Linux从而将应用程序与系统隔离开来。

因为每个Android应用都在一个进程沙盒中运行,所以应用必须请求访问沙盒以外的资源和数据。他们通过声明所需的权限来访问该权限,获得沙盒不提供的额外功能。根据敏感程度,系统可能自动授权,或者提示用户批准活拒绝请求。

很多时候授权失败,应用会抛出SecurityException。但是,不能保证到处都会发生。例如,sendBroadcast(Intent)方法调用后,该方法在数据传递给每个接收方是检查权限,因此如果授权失败你将不会收到异常。但是,几乎所有的情况,权限失败都将记录在系统日志中。

Android提供的权限在Manifest.permission中可以找到。任何应用也可以定义和强制自己的权限,因此这不是一个全面的所有权限列表。

在应用运行期间,很多地方可以执行特定的权限:

  • 再跳用系统是,阻止应用执行某些功能。
  • 当启动一个activity,要阻止应用启动其他应用的activity。
  • 发送和接受广播,控制谁能接收记得广播或谁能发送广播给你。
  • 访问和操作ContentProvider。
  • 绑定或启动一个service。

自动权限调整

随着时间的推移,新的限制可能被添加到平台,为了便于某些API,你的应用必须请求以前不需要的权限。因为现有的应用访问这些免费的权限,Android可能应用新的权限请求到应用的manifest中,避免在新的平台上破坏应用。Android根据targetSdkVersion属性来决定应用是否需要权限。如果值低于权限版本则Android会添加权限。

例如,WRITE_EXTERNAL_STORAGE权限已添加到API级别4中以限制对共享存储空间的访问。如果您的targetSdkVersion 值为3或更低,则会在新版本的Android中将此权限添加到您的应用程序中。

警告:如果你的应用自动添加了权限,即使你的应用可能不需要这些权限,Google Play也会列出来。(事实上限制各大市场也会检测)

为了避免这种情况,移除你不需要的权限,尽量保持targetSdkVersion在高版本。你能查看Build.VERSION_CODES档中每个版本添加的权限 。

安装未知应用所需权限

如果你的应用目标版本是Android 8.0(API level 26)或者更高版本,并使用安装API安装APK,你需要添加
REQUEST_INSTALL_PACKAGES权限。

<manifest>
    <uses-permission
        android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <application>
      ...
    </application>
</manifest>

只有使用此权限的应用才能出现在安装未知应用系统设置的屏幕上。参考install unknown apps

普通权限和危险权限

系统权限分为几个保护级别。要了解最重要的两个保护级别就是普通和危险权限:

  • 普通权限包括应用需要访问应用沙盒意外的数据或资源,但是用得隐私或其他应用的操作风险很小。例如,设置时区就是一个普通权限。如果应用声明一个它需要的普通权限,系统会自动授予应用。有关当前完整的普通权限列表参考普通权限
  • 危险权限包括应用需要涉及用户隐私的数据或资源,或可能影响用户存储的数据或其他应用的操作。例如,读取用户的通讯录就是一个危险权限。如果应用申明它需要的危险权限,用户必须明确授权。

特殊权限:有几个权限不具有普通和危险的权限。SYSTEM_ALERT_WINDOWWRITE_SETTINGS是特别敏感的,因此大多数应用不应该使用。如果应用需要其中之一,它必须在manifest中声明并发送intent请求用户授权。系统通过用户向用户显示纤细的管理屏幕来响应intent。具体参考SYSTEM_ALERT_WINDOWWRITE_SETTINGS

权限组

所有危险的Android系统权限都须臾权限组。如果设备运行Android 6.0并且应用的目标版本是23以上,当应用全球危险权限是遵循以下行为:

  • 如果应用请求manifest中列出的危险权限,并且该应用没有权限组中的权限,系统会向描述应用程序要访问的权限组的用户显示一个对话框。该对话框不描述该组内的特定权限。
  • 如果应用程序请求清单中列出的危险权限,并且应用程序已经在同一权限组中具有另一个危险权限,系统会立即授予权限,而不会与用户进行任何交互。

警告:未来的Android SDK可能将特定权限从一个组移动到另一个组。因此,不要将你的应用逻辑基于权限组的结构。

任何权限都可以属于权限组,包括由您的应用定义的普通权限和权限。但是,如果权限是危险的,权限组只会影响用户体验。您可以忽略正常权限的权限组。

如果设备运行Android 5.1(API级别22)或更低,或者应用程序的级别 targetSdkVersion为22或更低,则系统要求用户在安装时授予权限。系统再一次告诉用户应用程序需要什么权限组,而不是个人权限。

危险权限和权限组

暗含功能要求的权限

一些硬件和软件特征常量在相应的API之后被提供给应用程序; 例如android.hardware.bluetooth在Android 2.2(API级别8)中添加了该 功能,但在Android 2.0(API级别5)中添加了指向的Bluetooth API。正因为如此,有些应用程序才能够使用API​​,然后才能声明他们需要使用<uses-feature>系统的API 。

为了防止这些应用程序无意中出现,Google Play假定某些与硬件相关的权限表示默认情况下需要使用底层硬件功能。例如,使用蓝牙的应用程序必须BLUETOOTH 在<uses-permission>元素中请求权限- 对于旧版应用程序,Google Play假定权限声明意味着android.hardware.bluetooth应用程序需要基础功能,并根据该功能设置过滤。表2列出了暗示与<uses-feature>元素中声明的功能要求相同的权限 。

请注意,<uses-feature>声明(包括任何已声明的android:required属性)始终优先于表2中的权限所隐含的功能。对于这些权限中的任何一个,可以通过在<uses-feature>元素中明确声明隐含的功能来禁用基于隐含功能的过滤 一个 android:required="false"属性。例如,要禁用基于CAMERA权限的任何过滤,可以将此<uses-feature>声明添加 到清单文件中:

暗示设备硬件使用的设备权限

三、声明权限

每个Android应用在有限访问的沙盒中运行。如果应用需要用自己沙盒以外的资源或者信息,应用必须请求适当的权限。声明应用所需要的权限,在Manifest中列出这些权限。

根据权限的敏感程度,系统可能会自动授予权限,或者设备必须请求用户授予权限。例如,如果你的应用请求开始闪光灯权限,系统会自动授予权限。但是,如果你的应用需要读取用户的通讯录,系统会询问用户批准该权限。根据平台版本,在Android 5.1.1以及更低版本,用户在安装事授权,在Android 6.0以及更高版本在运行时授予权限。

确定你的App需要什么权限

开发App时,你应该注意App何时需要需要权限。通常,应用使用不是该应用创建的信息或资源,或应用执行需要设备(或其他应用)响应的行为。例如,如果应用需要访问网络,使用设备的相机,或者开关wifi应用都需要适当的授权。相关系统权限,请参考正常和危险权限

你的应用只在直接执行操作是需要权限。如果你的应用请求其他应用执行任务或提供信息,则不需要权限。例如,你的应用需要用户的地址簿,应用需要 READ_CONTACTS 权限。但是,你的应用用以个intent请求用户通讯录信息,你的应用不需要任何权限,但是通讯录应用需要这个权限。更多信息,参考使用意图

添加权限

声明应用需要的一个权限,在Manifest中<manifest>元素下添加一个<uses-permission>子元素。例如,应用需要发SMS消息,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>

声明权限之后系统的行为取决于权限敏感程度。如果权限不影响用户隐私,系统会自动授予权限。如果权限可能授予访问用户敏感信息,系统会询问用户批准请求。更多权限信息,参考正常和危险权限

四、运行时请求权限

从Android 6.0开始,当应用运行时用户授予权限给应用,而不是应用安装时。这个做法简化应用安装过程,因为安装或更新用影视用户不需要授予权限。让用户更好的控制应用的功能;例如,用户能选择给相机应用访问相机但是不能访问设备位置。在应的设置中,用户能随时撤销权限。

系统权限分为两类,正常和危险:

  • 正常权限不会直接威胁用户隐私。如果你的应用在manifest中列出正常权限,系统将自动授予这些权限。
  • 危险权限能使应用访问用户的机密数据。如果你的应用列出一个危险权限,用户必须给出明确批准。

在Android所有的版本中,应用需要声明正常权限和危险权限。但是,该声明的效果取决于系统版本和你应用的target SDK版本:

  • 如果设备系统低于Android 5.1(包含5.1),或者你的应用target SDK低于22(包含22):manifest中的危险权限,用户必须要安装应用时授予;如果不授予权限,系统就不会安装该应用。
  • 如果设备系统高于Android 6.0(包含6.0),并且你的应用target SDK高于23(包含23):应用必须在manifest中列出权限,并且在应用运行时必须请求每个危险权限。用户可以授予或拒绝每一个权限,即使用户拒绝权限请求应用也能继续以有限的权限运行。

注意:从Android 6.0(API level 23)开始,用户能随时撤销任何应用的权限,即使应用的target SDk版本较低。不管你的应用目标API是什么,你应该测试你的应用,以验证缺少所需权限时的行为正确。

检查权限

如果你的应用需要危险权限,每次执行需要权限的操作时你都必须检查是否有该权限。用户可以自有撤销权限,所以即使应用昨天使用了相机也不能认为今天仍然有权限。

要检查是否有权限,调用ContextCompat.checkSelfPermission()方法。例如,下面展示了如何检查活动是否有权限写入日历:

// 假设 thisActivity 是当前的 activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR);

如果应用有这个权限,该方法返回PackageMananger.PERMISSION_GRANTES,应用可以执行该操作。如果哦没有这个权限,该方法返回PREMISSION_DENIED,应用必须明确要求用户许可。

请求权限

如果你的应用需要在manifest中列出危险权限,则必须询问用户,授予权限。Android提供几种用来请求权限的方法。调用这些方法会弹出一个标准的dialog,你不能自定义。

图1.系统对话框提示用户授予或拒绝权限。

解释应用为什么需要权限

一些情况下,你可能想帮助用户理解,你的应用为什么需要权限。例如,如果用户运行一个相册应用,用户不会惊讶应用请求使用相机的权限,但是用户可能不理解为什么应用想访问用户的位置或者通讯录。在你请求一个全县之前,你应该考虑给用户一个解释。请记住,你不要想用解释镇压用户;如果你提供太多的解释,用户可能会泄气,并且卸载应用。

你可以在用户已经拒绝授权请求时提供一个说明。如果用户不断尝试需要权限的功能,但是一直拒绝权限请求,很可能表示用户不知道为什么应用需要给该功能提供权限。这种情况下,显示一个说明是个好主意。

为了帮助找出用户可能需要说的情况,Android提供了一个实用方法,shouldShowRequestPermissionRationale()。如果应用请求过此权限,用户拒绝了请求,该方法返回true

注意:如果用户拒绝过权限请求,而且在请求权限的系统对话框中选择"不再询问"选项,该方法返回false。如果设备策略禁止应用拥有该权限,此方法也会放回true

请求你需要的权限

如果你的应用还没有需要的权限,应用必须要调用requestPermissions()方法请求适当权限。应用传递想要的权限,也指定一个整型的request code标识这个权限请求。该方法异步执行:用户响应对话框之后它马上返回,系统调用应用的回调方法给予结果,并传递与requestPermissions()方法中相同的request code

以下代码检查应用是否有读取用户通讯录的权限,是否有必要请求权限:

//这里thisActivity是指当前的activity
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);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS 是应用定义的常量
        // 这个回调方法获取请求结果
    }
}

注意:当你的应用调用requestPermissions()方法,系统向用户展示一个标准的对话框。应用无法配置或改变该对话框。如果你向用户需要提供任何信息或说明,你应该在调用requestPermissions()方法之前,如解释应用需要权限的原因中所述。

处理权限请求响应

当应用请求权限是,系统向用户展示一个对话框。当用户响应是,系统执行应用的onRequestPermissionsResult()方法来传递用户的响应。应用必须重写此方法,确定用户是否授予该权限。回调传递与你传入requestPermissions()方法相同的request code。例如,应用请求READ_CONTACTS访问,它可能有一下的回调方法:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
    }
}

系统显示的对话框描述了应用需要访问的权限组;它没有列出特殊的权限。例如,如果你请求READ_CONTACTS权限,系统对话框只会说你的应用需要访问设备的通讯录。每个权限组用户只需要授予一次权限。如果你的应用请求该组中的任何其他权限在(已经在manifest中列出来),系统会自动授予它们权限。当请求权限是,如果用户通过系统对话框明确通过你的请求,系统会以相同的方式调用你的onRequestPermissionsResult()回调方法,并传递PERMISSION_GRANTED

注意:你的应该用需要明确的请求每个需要的权限,即使用户已经授权同一权限组的其他的权限,另外,在Android将来的版本中,权限的分组可能会发生变化。你的代码不应该依赖与特定的权限是否在同一组中。

例如,假设你在应用的manifest中列出READ_CONTACTSWRITE_CONTACTS权限。如果你请求READ_CONTACTS权限,并且用户授予权限,然后你请求WRITE_CONTACTS权限,系统会马上授予你权限而不会与用户交互。

如果用户拒绝了权限请求,你的应用应该采取适当的措施。例如,你的应用可以显示一个对话框,说明无法执行是因为需要用户操作该权限的请求。

当系统要询问用户授予权限时,用户可以选择该权限不再询问。这种情况下,应用任何时候再次调用requestPermissions()请求权限时,系统会马上拒绝。系统会马上调用onRequestPermissionsResult()回调方法,并传递PERMISSION_GRANTED。这意味着当你调用requestPermissions()方法时,你不能认为用户发生了直接的交互。

五、权限使用说明

应用很容易用权限请求逼迫用户,用户容易反感甚至是卸载应用。下面来看看如何避免不良的用户体验。

考虑使用Intetn

许多情况下,应用执行任务有两种方式。可以让应用自己请求执行所需的权限,也可以使用Intent让另一个应用执行任务。

假设你的应用需要通过相机获取一张图片。你的应用请求CAMERA权限允许应用直接访问相机。然后,应用能使用相机的API控制相机并获得图片。这种方式使你的应用完全控制摄影的整个过程,并让你把相机的UI整合到你应用中。

然而,你不需要完整的控制,你能用一个ACTION_IMAGE_CAPTURE的intent请求一个张图片。当你发送这个intent时,系统会提示用户选择相机应用(如果没有默认的相机应用)。用户通过所选择的相机获得图片,在应用的onActivityResult()方法中返回图片。

简单的说,如果你需要打电话,访问用户的通讯录等等,可以通过创建一个适当intent或者请求权限并直接访问相应对象。各种方式都有优缺点。

如果你使用权限:

  • 执行操作时,应用完全控制用户体验。但是,这会怎么任务的复杂性,因为你需要设计适合的用户界面。
  • 系统会在运行或者安装是提示用户授权一次(取决用户的Android版本)。之后,应用可以执行操作,无需影虎进行额外的交互。但是,如果用户不授予权限(或者稍后撤销),则应用无法执行操作。

如果你使用intent:

  • 你不需要为操作设计UI。处理intent的应用提供UI。但是,这意味着你布恩那个完全控制用户体验。用户可能和你从未见过的应用交互。
  • 如果用户没有该操作的默认应用,系统提示用户选择一个应用程序。如果用户没有默认处理程序,则每次执行操作会经过一个额外的对话框。

只询问你需要的权限

每次询问请求权限,你都会强迫用户做一个决定。你应该尽量减少请求次数。如果用户运行在Android 6.0或者更高的系统,每次用户尝试一些需要权限的新应用功能是,应用必须通过权限请求中断用户使用。如果用户使用更早的Andrdoi版本,当安装时用户必须授予每个应用的权限;如果列表太长或者看起来不合适,用户可能决定不安装你的应用。出于这些原因,你应该尽量减少应用需要权限的数量。

通常应用能避免通过intent请求权限。如果一个功能不是应用功能的核心部分,你应该考虑使用其他的应用处理。

不要压倒用户

在Android 6.0及其以上版本,用户必须要在运行是授予权限。如果用户一次面对太多权限,可能会压倒用户使用户退出应用。相反,你应该在需要是询问权限。

某项请款,一个或多个权限可能绝对重要。在应用启动后马上请求这些权限可能是有意义的。例如,你开发的摄影应用,应用需要访问相机。当用户第一次启动时,他们不会对请求使用相机权限感到惊讶。但是,如果相同的应用有通过通讯录分享图片的功能,你大概应该不会在第一次启动是询问READ_CONTACTS权限。而是等待用户尝试使用分享是在询问权限。

如果你的应用提供了一个教程,可能在教程结尾处请求应用的基本权限,这个可以有。

解释你需要权限的原因

当你调用requestPermissions()方法出现授权对话框,说需要什么权限,但不说原因。某些情况,用户可能困惑。在调用requestPermissions()方法前,解释你的应用为什么需要这个权限是一个好主意。

例如,一个摄影应用肯恩想使用定位服务,这样可以对相片进行地理标记。典型的,用户可能不明白一个相片可以包含位置信息,并会迷惑,为什么摄影应用想要知道定位。所以这种情况中,在程序调用requestPermissions()方法前向用户说明这个功能是一个好主意。

一种告知用户的方法时间这些请求合并到应用教程中。本教程能依次展示应用的功能,并可以解释需要什么权限。例如,摄影应用的教程可以演示其“与您的联系人共享照片”功能,然后告诉用户他们需要给应用程序权限以查看用户的联系人。应用程序可以调用requestPermissions()方法询问用户的访问权限。当然,并不是每个用户都会遵循这个教程,所以在应用程序的正常运行过程中,您仍然需要检查权限和请求权限。

测试两种权限模型

从Android 6.0(API级别23)开始,用户在运行时授予和撤销应用程序权限,而不是在安装应用程序时。因此,必须在更广泛的条件下测试您的应用程序。在Android 6.0之前,可以合理地假设,如果您的应用程序正在运行,则它拥有在应用程序清单中声明的所有权限。在新的权限模式下,您不能再做出这样的假设。

下列提示将帮助你识别运行API级别为23或更高的设备上与权限相关的代码问题:

  • 确定应用当前权限和相关的菜吗路径
  • 测试用户经过的全县保护服务和数据
  • 测试授权和撤销权限的各种组合。例如,相机应用可能会列出CAMERAREAD_CONTACTS以及ACCESS_FINE_LOCATION在其清单。您应该测试每个这些权限打开和关闭的应用程序,以确保应用程序可以处理所有权限配置优雅。请记住,从Android 6.0开始,用户可以为任何应用程序打开或关闭权限,甚至可以打开目标API级别为22或更低的应用程序。
  • 使用adb工具从命令行管理权限:
    • 按组列出权限
      $ adb shell pm list permissions -d -g
      
    • 授予(或拒绝)一个(或多个)权限
      $ adb shell pm [grant|revoke] <permission-name> ...
      
  • 分析您的应用程序中使用权限的服务。

相关文章

网友评论

    本文标题:Android 运行时权限官方指南

    本文链接:https://www.haomeiwen.com/subject/czppbxtx.html