Delphi专题 ·

Delphi安卓申请运行时(动态)权限(Runtime permissions)

介绍

Delphi自Xe5以来,在Android中引入了对应用程序权限的支持。这对于应用程序在Android中成功运行是非常必要的。例如,如果您需要让应用程序知道用户的位置,则必须在“project options”对话框中选择相关权限:
Delphi安卓申请运行时(动态)权限(Runtime permissions) Delphi专题 第1张
指定的权限将被发送到应用程序清单文件中,并且可以根据系统的要求进行检查。在项目的AndroidManifest.template.xml文件中有下边的权限占位符:

在工程的构建过程中,它将扩展到生成的AndroidManifest.xml文件中,以便打包到生成的.apk文件中:

对于使用Delphi 10.2.3及更早版本构建的应用程序,在安装应用程序期间建立/验证这种方式请求的权限。例如,当从Google Play Store下载某个应用程序时,用户会被告知所需的权限,他们可以据此接受或拒绝安装。
这个设置已经为Delphi应用程序提供了足够好的服务,直到谷歌改变了参与规则。
从Android6.0(Android Mashmallow)开始,更具体地说,应用程序运行在Android API level 23或更高级别时,有一个新的运行权限:运行时权限(Runtime Permissions)。这个方法规定了对于被视为危险权限的权限子集,应用程序必须采取某些步骤。(危险权限)Dangerous permissions涵盖的一系列权限的共同点是:这些权限会读写用户的隐私信息,也可能会读写用户存储的数据或影响其他App的正常运行。包括读取写入通讯录,使用相机或麦克风、访问用户的位置等等。
如果您需要在需要授予危险权限的应用程序中执行某些操作,则必须像以前一样在清单文件中指定该操作,但此外,应用程序必须在运行时请求该权限:必须提示用户授予该权限,当然他们可以允许或拒绝该权限。在用户拒绝请求的权限的情况下,应用程序必须确保行为适当。除此之外,在设备上的应用程序设置中,可以根据用户的要求授予或拒绝危险权限,这意味着以前授予的权限很可能在下次需要时被拒绝。
如果不使用Android 6.0或更高版本,这是一个可选方案,它不会影响到你。Delphi10.2.3和更早的API Level 14(Android 4.0)等没有受到影响。
然而,在2017年底谷歌宣布2018年规则将会改变。为了向Google Play Store提交应用程序,他们现在要求必须为API Level 26 (Android 8.0) 或更高版本。遵守这个新的要求意味着Delphi应用程序现在需要使用运行时权限模型,这是他们在Delphi10.3rio之前无法做到的。
所有这些新的必需行为都需要在应用程序中为受影响的(危险的)权限添加新的(或稍微修改的)代码,并在FireMonkey框架和RTL(运行时库)的Android部分添加一些关联的新代码。RAD Studio10.3 Rio增加了所需的支撑,以实现这些。
本文将研究需要什么,并指导您使用Delphi 10.3 Rio在应用程序中实现运行时权限的过程。

在Delphi中实现运行时权限

在最低level Android运行时权限需要以下几点:

  • 首先,您必须在需要许可的代码之前请求许可。
  • 其次,当用户的响应返回时(作为Delphi应用程序底层活动的回调方法),需要适当地处理它。

假设许可被授予并且没有被拒绝,那么程序就可以继续做它想做的事情。当然,如果程序试图在未获得许可的情况下继续运行,那么Android操作系统将抛出某种安全异常,代码将无法继续运行。对于在早期版本的Delphi中开发的应用程序,您可能需要更改逻辑流程以将其考虑在内。
如前所述,处理活动回调是最低级别的需求。在Delphi 10.3 Rio(及更高版本)中,表示Activity的代码确实响应了所需的回调Activity.onRequestPermissionsResult。这将使用RTL消息订阅系统(通过TPermissionsRequestResultMessage消息)向任何相关方广播结果,类似于它如何显示OnActivityResult和OnNewIntent回调(分别通过TMessageResultNotification和TMessageReceivedNotification消息)。
当您想在Delphi 10.3应用程序中解决运行时权限的需求时,可以采用以下两种方法之一:

  • 使用android api请求权限,从android回调订阅上述RTL广播消息,并编写一些消息接收代码,以获取用户响应并采取相应的行动。
  • 使用一个新的跨平台功能(尽管目前仅在Android上实现)RTL权限框架来完成请求和响应接收。

通常,强烈建议您使用新的权限框架,当然,所有需要运行时权限才能工作的演示都使用通过RTL中的System.Permissions单元提供的权限框架。不过,为了完整起见,我将在本文中展示这两种方法。我将从使用新框架的建议方法开始,然后在最后我将介绍较低级别的方法,如果您不感兴趣,可以跳过这些方法。

通过RTL权限框架获得运行时权限Runtime permissions(推荐)

为了了解我们如何实现对运行时权限的请求和响应,假设我们有一个应用程序想要知道用户的位置,在TMapView控件上显示,将地图置于用户位置的中心,并呈现一个与用户在地图上的位置近似的圆。在Android这意味着我们需要授予访问粗糙位置ACCESS_COARSE_LOCATION和访问精细位置ACCESS_FINE_LOCATION的权限。
在深入了解细节之前,我将指出使用地图控件的应用程序(如此示例应用程序)需要启用以下几项:

  • 在菜单project——options项目中找到Version Info部分,API Key条目必须设置为您的谷歌地图api密钥。您可以按照官方文档中的说明获得API密钥。在这个屏幕截图中,我清楚地指出了您在哪里输入API密钥。如果你不这样做,最好的情况是你的地图将不会显示,最坏的情况是当你试图显示地图时,你的应用程序将无法安装或者崩溃。
  • Delphi安卓申请运行时(动态)权限(Runtime permissions) Delphi专题 第2张

  • 在工程菜单—project—options “Entitlement list”部分,需要启用“Maps Service”服务条目。这样可以确保将适当的条目插入应用程序清单文件的部分(显然,上一条提到的输入的API key将被使用):
  • Delphi安卓申请运行时(动态)权限(Runtime permissions) Delphi专题 第3张

简单的初始化方法

请求用户权限的代码如下所示。地图设置代码在SetupMap过程中,如果传递为true,则会将其添加到用户位置选项中;如果传递为false,则不会添加用户位置选项。但是,在这个代码段中这些都不可见,因为我们必须等待用户对权限请求的响应。
我们可以看到,如果权限已经被授予,或者我们正在一个早于Android 6.0的操作系统上运行(SDK版本23,即API level 23),那么只需使用参数值true执行SetupMap过程。否则,我们运行代码来请求权限,在知道用户决定了什么之前不做任何进一步的操作。

当用户确实响应权限请求时,有关该请求的信息将传递到过程LocationPermissionRequestResult(利用10.3 Rio中添加的新内联变量和类型推断)。在这里,您可以看到前面提到的SetupMap过程现在使用一个与权限授予状态相适应的布尔值进行调用。

如您所见,权限的授予状态将被检查,然后这将控制传递到SetupMap调用的参数。
注意:建议PermissionsService.RequestPermissions方法有一个匿名过程的参数。一些Demo证实了这一点,包括Object Pascal/Mobile Snippets/Location,其中包含以下代码:

更复杂,更正确的方法

如果应用程序走下一条从拒绝他们用户请求权限的执行路径,那就好了。我们可以采取相应的行动。
如果应用程序随后走上相同的执行路径,在没有任何形式的额外解释的情况下再次向用户请求权限(可能会一次又一次),那么这可能会让用户感到非常恼火或沮丧。
鉴于这种可能性,谷歌建议在询问第二次和后续次数考虑到这一点。这可以非常直接地完成,只需对首先请求权限的调用进行一个小的更改。有一个可选的过程参数可以传递,如下所示。如果这样做是有意义的,那么将调用该例程,并期望它向用户显示一条附加的解释性消息,解释为什么我们需要该权限。换言之,该应用程序可以提供一个理由,解释为什么许可是重要的,并被再次要求。
请注意,此说明性消息必须是异步的。另外,请注意,在这种情况下,被调用的过程DisplayRational传递了一个过程参数,在用户从屏幕上取消解释后必须调用该参数。这一基本原理后程序继续请求再次许可。鉴于用户已经看到关于许可请求的解释性信息,我们可能会从他们那里得到更有利的响应。

要了解权限框架的操作方式,您应该浏览delphi自带的项目Demo,这些Demo位于下面文件夹中(只需在资源管理器地址栏中复制以下路径):%PUBLIC%\Documents\Embarcadero\Studio\20.0\Samples\Object Pascal
需要运行时权限的内容已相应更新,包括:

  • Object Pascal/Mobile Snippets/AccessCameraApp
  • Object Pascal/Mobile Snippets/AudioRecPlay
  • Object Pascal/Mobile Snippets/CameraComponent
  • Object Pascal/Mobile Snippets/CameraRoll
  • Object Pascal/Mobile Snippets/Location
  • Object Pascal/Mobile Snippets/PhoneDialer
  • Object Pascal/Mobile Snippets/ShareSheet
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/Address Book/BirthdayReminder
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/Address Book/Contacts
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/App Tethering/PhotoWall/Mobile
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/Bluetooth/BLEScanner
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/FlashLight
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/Map Type Selector
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/SensorInfo
  • Object Pascal/Multi-Device Samples/Media/MusicPlayer
  • Object Pascal/Multi-Device Samples/Media/PhotoEditorDemo
  • Object Pascal/RTL/Tethering/PhotoWall/Mobile

相机操作和文件共享

为了完全接受来自Google的所有与权限相关的新规则和规定,我们必须了解的不仅仅是设置简单的权限(并适当地请求它们)。当 Android 7.0(Android Nougat)出现时,一些安全漏洞措施被收紧,这对任何针对android 7.0或更高版本的应用程序都产生了连锁反应。由于Delphi 10.3现在的目标是Android 8.0 SDK级别,这也会影响Delphi应用程序,所以我们需要注意。
这个特殊的变化会影响到其中一个类,这个类用来用照相机拍摄照片,并在你的应用程序中接收它,而这成为一个问题,因为类需要做什么以及它是如何做的。在FireMonkey中,TTakePhotoFromCameraAction标准操作组件执行此任务。该组件的工作方式是使用一条指令启动相机应用程序,以拍摄照片并将其存储在一个文件中,该文件的标识(文件URI)将传递给安卓目标对象中的相机应用程序。正是后一点触犯了新的规则。Android 7.0不再允许您在应用程序外部公开file://类型的uri,因此将引用传递到相机应用程序的尝试很快就会得到响应,并且会出现安全异常。
要在允许的指导方针范围内操作,需要两个步骤。

  • 第一步是将URI从file:// URI 更改为content:// 。
  • 第二步是允许摄像头应用程序使用文件提供程序FileProvider临时访问该URI后面的资源。

第一步是在FireMonkey Java RTL代码中清理。不幸的是,第二步,使用文件提供程序,需要向应用程序清单文件添加附加信息,还需要部署附加的XML资源文件。这一切都需要小心谨慎的运用,但幸运的是,Delphi 10.3 Rio使它完全简单明了,在 菜单—project—options—Entitlement list中提供了一个新的Android特定的应用程序授权。
权限实际上是宏观的选择。您启用了一个单一的权限,结果对您的项目执行了许多操作。在这种情况下,清单将得到一个部分,并将一个新的provider_paths.xml资源文件添加到Android resources文件夹树中。
如果显式或隐式使用TTakePhotoFromCameraAction,则需要启用安全文件共享权限:
Delphi安卓申请运行时(动态)权限(Runtime permissions) Delphi专题 第4张
如果您忘记启用安全文件共享(Secure File Sharing)权限,则会收到这种几乎无法穿透的运行时异常(我说几乎无法穿透,因为在不同论坛上有各种关于它的发布问题,所有这些问题都表明所需清单文件部分和资源文件都必须存在):
Delphi安卓申请运行时(动态)权限(Runtime permissions) Delphi专题 第5张
这些产品演示已通过启用安全文件共享权限进行更新:

  • Object Pascal/Mobile Snippets/AccessCameraApp
  • Object Pascal/Mobile Snippets/CameraRoll
  • Object Pascal/Mobile Snippets/ShareSheet
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/Address Book/Contacts
  • Object Pascal/Multi-Device Samples/Device Sensors and Services/App Tethering/PhotoWall/Mobile
  • Object Pascal/Multi-Device Samples/Media/MusicPlayer
  • Object Pascal/Multi-Device Samples/Media/PhotoEditorDemo
  • Object Pascal/RTL/Tethering/PhotoWall/Mobile

使用android api请求权限

简单的初始化方法

参与评论