Xamarin.Android 中的权限

概述

Android 应用程序在其自己的沙盒中运行,出于安全原因无法访问设备上的某些系统资源或硬件。 用户必须显式授予对应用的权限,然后才能使用这些资源。 例如,未经用户的明确权限,应用程序无法访问设备上的 GPS。 如果应用尝试在没有权限的情况下访问受保护的资源,Android 将引发 Java.Lang.SecurityException

开发应用时,应用程序开发人员在 AndroidManifest.xml 中声明权限。 Android 有两个不同的工作流,用于获取用户对这些权限的同意:

  • 对于面向 Android 5.1 (API 级别 22) 或更低级别的应用,权限请求是在安装应用时发生的。 如果用户未授予权限,则不会安装该应用。 安装应用后,除非卸载应用,否则无法撤销权限。
  • 从 Android 6.0 (API 级别 23) 开始,用户可以更好地控制权限;只要在设备上安装了应用,他们就可以授予或撤销权限。 此屏幕截图显示了 Google 联系人应用的权限设置。 它列出了各种权限,并允许用户启用或禁用权限:

“示例权限”屏幕

Android 应用必须在运行时检查,以查看它们是否有权访问受保护的资源。 如果应用没有权限,则必须使用 Android SDK 提供的新 API 发出请求,以便用户授予权限。 权限分为两类:

  • 普通权限 – 这些权限对用户的安全或隐私构成很小的安全风险。 Android 6.0 将在安装时自动授予正常权限。 有关 正常权限的完整列表,请参阅 Android 文档。
  • 危险权限 – 与普通权限相比,危险权限是保护用户安全或隐私的权限。 这些必须由用户显式授予。 发送或接收短信是需要危险权限的操作示例。

重要

权限所属的类别可能会随时间而更改。 在将来的 API 级别,被归类为“正常”权限的权限可能会被提升为危险权限。

危险权限进一步细分为 权限组。 权限组将保存逻辑上相关的权限。 当用户向权限组的一个成员授予权限时,Android 会自动向该组的所有成员授予权限。 例如, STORAGE 权限组同时 WRITE_EXTERNAL_STORAGE 包含 和 READ_EXTERNAL_STORAGE 权限。 如果用户向 READ_EXTERNAL_STORAGE授予权限, WRITE_EXTERNAL_STORAGE 则同时自动授予权限。

在请求一个或多个权限之前,最佳做法是在请求权限之前提供应用为何需要权限的理由。 一旦用户了解了理由,应用就可以向用户请求权限。 通过了解基本原理,用户可以在想要授予权限时做出明智的决定,并了解他们不授予权限时产生的反响。

检查和请求权限的整个工作流称为运行时权限检查,可以总结为下图:

运行时权限检查流程图

Android 支持库向后移植一些新 API,以获取对旧版 Android 的权限。 这些向后移植的 API 将自动在设备上检查 Android 版本,因此无需每次执行 API 级别检查。

本文档将讨论如何向 Xamarin.Android 应用程序添加权限,以及面向 Android 6.0 (API 级别 23) 或更高版本的应用应如何执行运行时权限检查。

注意

硬件权限可能会影响 Google Play 筛选应用的方式。 例如,如果应用需要相机权限,则 Google Play 不会在未安装相机的设备上在 Google Play 商店中显示该应用。

要求

强烈建议 Xamarin.Android 项目包含 Xamarin.Android.Support.Compat NuGet 包。 此包会将权限特定的 API 向后移植到较旧版本的 Android,提供一个通用接口,而无需不断检查运行应用的 Android 版本。

请求系统权限

使用 Android 权限的第一步是在 Android 清单文件中声明权限。 无论应用面向的 API 级别如何,都必须执行此操作。

面向 Android 6.0 或更高版本的应用不能假定,因为用户在过去某个时间点授予了权限,因此该权限将在下次生效。 面向 Android 6.0 的应用必须始终执行运行时权限检查。 面向 Android 5.1 或更低版本的应用不需要检查执行运行时权限。

注意

应用程序应仅请求所需的权限。

在清单中声明权限

使用 元素将权限添加到 AndroidManifest.xmluses-permission 。 例如,如果应用程序要定位设备的位置,则需要良好的和路线位置权限。 将以下两个元素添加到清单:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

可以使用 Visual Studio 中内置的工具支持来声明权限:

  1. 双击解决方案资源管理器中的“属性”,然后在属性窗口中选择“Android 清单”选项卡:

    Android 清单选项卡中所需的权限

  2. 如果应用程序还没有AndroidManifest.xml,请单击“ 找不到AndroidManifest.xml”。单击以添加一个 ,如下所示:

    无AndroidManifest.xml消息

  3. 从“所需权限”列表中选择应用程序所需的任何 权限 ,然后保存:

    所选相机权限示例

Xamarin.Android 将在生成时自动向调试版本添加一些权限。 这将简化应用程序调试。 具体而言,两个值得注意的权限是 INTERNETREAD_EXTERNAL_STORAGE。 这些自动设置的权限不会出现在 “所需权限 ”列表中启用。 但是,发布版本仅使用在 “所需 权限”列表中显式设置的权限。

对于面向 Android 5.1 (API 级别 22) 或更低级别的应用,无需执行其他操作。 将在 Android 6.0 (API 23 级别 23) 或更高版本上运行的应用应继续下一部分,了解如何执行运行时权限检查。

Android 6.0 中的运行时权限检查

ContextCompat.CheckSelfPermission Android 支持库) (可用于检查是否授予了特定权限。 此方法将返回一个 Android.Content.PM.Permission 枚举,其中包含以下两个值之一:

  • Permission.Granted – 已授予指定权限。
  • Permission.Denied – 尚未授予指定权限。

此代码片段是一个示例,说明如何在活动中检查相机权限:

if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == (int)Permission.Granted) 
{
    // We have permission, go ahead and use the camera.
} 
else 
{
    // Camera permission is not granted. If necessary display rationale & request.
}

最佳做法是告知用户为什么应用程序需要权限,以便可以做出明智的决定来授予权限。 其中一个示例是拍摄照片和地理标记的应用。 用户很清楚相机权限是必需的,但可能不清楚应用为什么还需要设备的位置。 理由应显示一条消息,以帮助用户了解为什么需要位置权限以及需要相机权限。

方法 ActivityCompat.ShouldShowRequestPermissionRationale 用于确定是否应向用户显示理由。 如果应显示给定权限的理由,则此方法将返回 true 。 此屏幕截图显示了应用程序显示的 Snackbar 示例,其中解释了应用为何需要知道设备的位置:

位置理由

如果用户授予权限, ActivityCompat.RequestPermissions(Activity activity, string[] permissions, int requestCode) 则应调用 方法。 此方法需要以下参数:

  • activity – 这是请求权限的活动,由 Android 通知结果。
  • 权限 – 正在请求的权限的列表。
  • requestCode – 一个整数值,用于将权限请求的结果与 RequestPermissions 调用匹配。 此值应大于零。

此代码片段是讨论的两种方法的示例。 首先,进行检查以确定是否应显示权限理由。 如果要显示基本原理,则会显示带有理由的 Snackbar。 如果用户在 Snackbar 中单击 “确定” ,则应用将请求权限。 如果用户不接受理由,则应用不应继续请求权限。 如果未显示理由,则活动将请求权限:

if (ActivityCompat.ShouldShowRequestPermissionRationale(this, Manifest.Permission.AccessFineLocation)) 
{
    // 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 user has previously denied the permission.
    Log.Info(TAG, "Displaying camera permission rationale to provide additional context.");

    var requiredPermissions = new String[] { Manifest.Permission.AccessFineLocation };
    Snackbar.Make(layout, 
                   Resource.String.permission_location_rationale,
                   Snackbar.LengthIndefinite)
            .SetAction(Resource.String.ok, 
                       new Action<View>(delegate(View obj) {
                           ActivityCompat.RequestPermissions(this, requiredPermissions, REQUEST_LOCATION);
                       }    
            )
    ).Show();
}
else 
{
    ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.Camera }, REQUEST_LOCATION);
}

RequestPermission 即使用户已授予权限,也可以调用 。 后续调用不是必需的,但它们为用户提供了确认 (或撤销权限) 的机会。 调用 时 RequestPermission ,控件将移交给操作系统,操作系统将显示用于接受权限的 UI:

Permssion 对话框

用户完成后,Android 将通过回调方法 OnRequestPermissionResult将结果返回到 Activity。 此方法是 接口 ActivityCompat.IOnRequestPermissionsResultCallback 的一部分,必须由 活动实现。 此接口有一个 方法, OnRequestPermissionsResultAndroid 将调用该方法,以通知活动用户的选择。 如果用户已授予权限,则应用可以继续使用受保护的资源。 下面显示了如何实现 OnRequestPermissionResult 的示例:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    if (requestCode == REQUEST_LOCATION) 
    {
        // Received permission result for camera permission.
        Log.Info(TAG, "Received response for Location permission request.");

        // Check if the only required permission has been granted
        if ((grantResults.Length == 1) && (grantResults[0] == Permission.Granted)) {
            // Location permission has been granted, okay to retrieve the location of the device.
            Log.Info(TAG, "Location permission has now been granted.");
            Snackbar.Make(layout, Resource.String.permission_available_camera, Snackbar.LengthShort).Show();            
        } 
        else 
        {
            Log.Info(TAG, "Location permission was NOT granted.");
            Snackbar.Make(layout, Resource.String.permissions_not_granted, Snackbar.LengthShort).Show();
        }
    } 
    else 
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

总结

本指南讨论了如何在 Android 设备中添加和检查权限。 (API 级别 < 23) 的旧 Android 应用和新 Android 应用的权限的工作方式差异 (API 级别 > 22) 。 它讨论了如何在 Android 6.0 中执行运行时权限检查。