安卓-6-基础知识-全-

安卓 6 基础知识(全)

原文:zh.annas-archive.org/md5/548B3093CDA1BA59E864B1DE126184AD

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Android 6 主要关注改善整体用户体验,并引入了一些功能,例如重新设计的权限模型,在该模型中,应用程序在安装时不再自动获得其指定权限,设备在用户未操作时采用 Doze 电源方案以延长电池寿命,以及原生支持指纹识别。

如果你已经是 Android 开发者,只需几步之遥就能利用你现有的开发经验,无论用户在任何时间、任何地点需要或想要你的 app,你都能触达他们。

作为一名专业的 Android 开发人员,你必须为用户创建可用于生产的 app。这本书将为你提供作为公司开发团队的一员、独立 app 开发者或仅作为使用 Android 开发最佳实践的程序员发布精致 app 所需要的一切。

在本书的最后,你将能够识别应用程序中需要改进的关键领域,并实施必要的更改和完善,以确保在发布前符合 Android 核心应用指南。

本书涵盖的内容

第一章,Android Marshmallow 权限,讨论了 Android 权限系统和模型非常广泛,并且进行了一些可以帮助应用开发者和应用程序获得更多吸引力、安装量,以及让用户决定你的应用程序何时能够使用每个依赖权限功能的更改。但请记住,这只是一个起点,Android Marshmallow 仍需要获得市场份额并得到 OEM 厂商的采用,使用户能够自由选择。作为应用开发者,你必须提前做好准备,确保你的应用开发面向未来,让新用户尽快享受最新的更新,同时保持你的应用程序的高性能水平。

第二章,应用链接,讲述了应用链接在 Android Marshmallow 中变得多么强大。这允许你,应用开发者,帮助系统更好地决定如何行动。处理网页 URL 将为你提供更广泛的曝光,更多进入你 app 的渠道,以及你可以为用户提供更好的体验(这都有助于提高评分和增加下载量,反之亦然)。

应用链接实现简单,易于理解,在当今的移动/网络世界中是必备功能。尽管应用链接为使用你应用程序的用户提供了更好的操作处理,但用户可能会有多个设备,期望在每个设备上都有相同的行为,如果他们的数据和操作处理能够全方位的话,他们会更加投入。

第三章,应用自动备份,告知你 Android Marshmallow 带来了一项为应用备份的出色功能,这减少了用户迁移到新设备时的摩擦。

在一个充满各种不同应用的世界里,最大化自动备份的好处可以带来极佳的用户体验。这一功能的目标是减轻负担,缩短设置新设备所需的时间,并安装用户喜爱的应用。如果需要,在全新安装后,仅通过密码提示就让用户进入你的应用,可以提供很好的体验。试试看吧!

第四章,变革展开,概述了 Android Marshmallow 中的一些变化。所有这些变化都值得关注,并将在你的应用开发周期中提供帮助。未来章节将讨论更多变化,并采用更详细的方法。

第五章,音频、视频和相机功能,涵盖了 Android API 的许多变化和新增内容。Android Marshmallow 更多的是帮助我们开发者实现更好的媒体支持,在使用音频、视频或相机 API 时展示我们的创意。

第六章,工作用 Android,涵盖了 Android Marshmallow 为工作用 Android 世界带来的诸多变化。作为开发者,我们需要始终与组织的需要保持可行的联系。确保我们通过 Marshmallow 的变化了解工作用 Android 的世界,可以帮助我们构建并针对企业工作流程,同时享有更简单 API 的好处。

第七章,Chrome 自定义标签页,讨论了新增加的功能,Chrome 自定义标签页,它允许我们开发者将网页内容嵌入到我们的应用中,修改 UI,并根据我们应用的主题和色彩以及观感进行调整。这帮助我们让用户留在我们的应用中,同时提供良好的 UI 和整体体验。

第八章, 认证, 讨论了 Android Marshmallow 如何通过指纹 API 为我们提供了一个新的用户认证接口。我们可以使用传感器,甚至在我们的应用程序内对用户进行认证,并在需要时保存,以避免使用 Android Marshmallow 引入的凭据宽限期功能来登录用户。我们还介绍了一种方法,通过仅使用 HTTPS 来提高应用程序的安全性。借助 usesCleartextTraffic 标志实施的 StrictMode 策略,可以确保我们连接到外部世界的所有节点都经过检查,以确认是否需要安全连接。

你需要为这本书准备什么

对于这本书,你需要具备 Android 平台、API 以及应用程序开发过程的前期知识。你还需要设置工作环境,至少具备以下条件:

  • 可以从developer.android.com/sdk/index.html下载的 Android Studio

  • 最新的 Android SDK 工具和平台。请确保升级到最新版本,如果缺少的话,添加 Android 6.0(Marshmallow)平台。

  • 安卓设备很有帮助,但如果你愿意,也可以使用模拟器,或者你可以使用 Genymotion 的出色解决方案作为模拟器,访问www.genymotion.com/

这本书的目标读者

这本书是为希望轻松地将应用程序迁移到下一个 Android 版本的安卓开发者准备的。在本书的章节中,作者将 Android 6 称为 Android Marshmallow。你应该对 Java 和之前的 Android API 有很好的了解,并且应该能够使用 Marshmallow 之前的 API 编写应用程序。

约定

在这本书中,你会发现多种文本样式,用于区分不同类型的信息。以下是一些样式示例及其含义的解释。

文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理程序如下所示:"添加了setTorchMode()方法来控制闪光灯手电筒模式。"

代码块设置如下:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
  <exclude domain="database" path="sensitive_database_name.db"/>
  <exclude domain="sharedpref" path="androidapp_shared_prefs_name"/>
  <exclude domain="file" path="some_file.file_Extension"/>
  <exclude domain="file" path="some_file.file_Extension"/>
</full-backup-content>

任何命令行输入或输出都如下所示:

$ adb shell sm set-force-adoptable true

新术语重要词汇以粗体显示。你在屏幕上看到的内容,例如菜单或对话框中的,会像这样出现在文本中:"现在当你前往设置 | 更多 | VPN时,你可以查看 VPN 应用。"

注意

警告或重要提示会像这样出现在一个框里。

提示

提示和技巧会像这样出现。

读者反馈

我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢或不喜欢什么。读者的反馈对我们很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。

如需向我们提供一般性反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书籍标题。

如果您在某个主题上有专业知识,并且有兴趣撰写或参与书籍编写,请查看我们的作者指南www.packtpub.com/authors

客户支持

既然您已成为 Packt 图书的骄傲拥有者,我们有一系列措施帮助您充分利用您的购买。

下载示例代码

您可以从您的账户www.packtpub.com下载您购买的所有 Packt Publishing 书籍的示例代码文件。如果您在别处购买了这本书,可以访问www.packtpub.com/support注册,我们会将文件直接通过电子邮件发送给您。

下载本书的颜色图像

我们还为您提供了一份 PDF 文件,其中包含本书中使用的屏幕截图/图表的颜色图像。颜色图像可以帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/4412OS_ColoredImages.pdf下载此文件。

勘误

尽管我们已经尽力确保内容的准确性,但错误仍然会发生。如果您在我们的书中发现了一个错误——可能是文本或代码中的错误——如果您能报告给我们,我们将不胜感激。这样做,您可以避免其他读者感到沮丧,并帮助我们改进该书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误被验证,您的提交将被接受,勘误信息将被上传到我们的网站或添加到该标题勘误部分下的现有勘误列表中。

若要查看之前提交的勘误信息,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将显示在勘误部分下。

盗版

网络上对版权材料的盗版是所有媒体面临的持续问题。在 Packt,我们非常重视保护我们的版权和许可。如果您在互联网上以任何形式发现我们作品非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

如发现疑似盗版材料,请通过<copyright@packtpub.com>联系我们,并提供相关链接。

我们感谢您帮助保护我们的作者以及我们提供有价值内容的能力。

问题

如果您对这本书的任何方面有问题,可以通过<questions@packtpub.com>联系我们,我们将尽力解决问题。

第一章:Android 棉花糖权限

Android 权限自从我们记忆中的 Android 1.0 版本以来就一直存在。多年来,随着平台的演变,Android 权限模型通过添加新权限,试图允许更细粒度地控制应用程序对设备硬件/数据的访问部分。

在本章中,我们将回顾一下Android 棉花糖之前的 Android 权限模型,并关注它带来的变化。我们还将解释作为开发人员你必须做的更改,以处理所有其他变化,并确保你的应用程序在 Android 棉花糖上按预期工作。

在本章中,我们将涵盖以下内容:

  • Android 权限概述

  • 理解 Android 棉花糖权限

  • 使用最佳实践处理代码权限

Android 权限概述

在 Android 操作系统中,每个应用程序都使用独特的系统 ID 运行,这些 ID 被称为Linux 用户 ID组 ID。系统的各个部分也被分配了不同的 ID,为应用程序形成了与其他应用及系统隔离的区域。作为这一隔离生命周期方案的一部分,访问服务或其他应用程序数据需要提前声明这一需求,即请求相应的权限。

这可以通过在AndroidManifest.xml文件中添加uses-permission元素来实现。你的清单可以有零个或多个uses-permission元素,它们都必须是根元素<manifest>的直接子元素。

尝试在没有适当权限的情况下访问数据或功能,通常会抛出安全异常(使用SecurityException类),告知我们缺少权限。

sendBroadcast(Intent)方法比较特殊,因为它在方法调用返回后检查权限,所以如果出现权限失败,我们不会收到异常。权限失败应该被记录到系统日志中。需要注意的是,在 Android 棉花糖版本之前,缺失权限是由于在清单中缺少声明。因此,在制定应用功能列表时,你应牢记权限问题。

权限

当使用 Android 平台作为应用程序时,你将受到限制,无法访问某些硬件、系统 API、私人用户数据和应用程序数据。

为了允许访问特定的 API、数据或硬件,需要权限;在安装应用直到 Android 棉花糖版本时,会请求权限。大多数权限用于限制访问。当权限被授予时,你就可以访问到特定的受限区域。一个功能最多可以被一个权限保护。

uses-permission元素需要一个名为android:name的属性,这是你的应用程序所需的权限名称:

<uses-permission android:name="string" android:maxSdkVersion="integer" />

你知道在 API 级别 19 中添加的android:maxSdkVersion属性,用于通知从哪个 API 版本开始不再授予此权限吗?如果权限在更高版本的 API 中不再需要,这很有用。例如,请看以下内容:

<uses-permission
  android:name="android.permission.READ_EXTERNAL_STORAGE"
  android:maxSdkVersion="18" />

在 API 19 中,你的应用无需请求此权限——它会自动授予你。

你的应用还可以通过权限保护自己的组件,如活动、服务、广播接收器和内容提供者。

它可以使用 Android 定义的任何权限,也可以使用其他应用声明的权限,或者可以定义自己的权限。

有关权限的更多信息,你可以阅读developer.android.com/reference/android/Manifest.permission.html

权限组定义

权限被划分为多个组。根据谷歌的说法,我们可以认为权限组是将相关权限组合在一起的单个名称/标签。你可以在<permission>元素内的permissionGroup属性中组合权限。

当批准权限或检查应用的权限时,同一权限组中的权限将显示为一个组。

权限组是在从 Play 商店安装应用时你所看到的内容;例如,请看以下截图:

权限组定义

让我们来看一下permission-group标签的结构:

<permission-group android:description="string resource"
                  android:icon="drawable resource"
                  android:label="string resource"
                  android:name="string" />

前述结构的元素可以解释如下:

  • android:description:这指的是用于描述该组的简单文本。

  • android:icon:这指的是一个可绘制资源中的图标,代表该权限。

  • android:label:这指的是一个简单文本名称,用于标识该组。

  • android:name:这是组的名称。它用于将权限分配给特定的组。

下表展示了权限组中存在的各种权限类别:

权限组
应用内购买
联系人
电话
Wi-Fi 连接信息
身份
短信
麦克风
设备 ID 和通话信息
其他

注意

任何不属于权限组的权限将被显示为其他。当应用更新时,该应用的权限组可能会有所变化。

暗示功能需求的权限

某些权限是由功能需求暗示的;我们将在下一部分介绍这一点。

当在清单中声明一个功能时,我们还必须请求所需的权限。

举例来说,如果我们想要有一个为联系人设置图片的功能。如果我们想通过Camera API 拍照,那么我们就必须请求一个Camera权限。

<users-feature>标签确保我们声明需要支持我们应用程序工作并使用该功能所需特性的设备。如果这个特性不是必需的,并且我们的应用程序可以在没有它的情况下工作但功能较少,我们可以使用android:required="false",记住这个特性是可选的。

<uses-feature>声明总是优先于由权限暗示的特性。可以在此找到暗示特性要求的权限类别完整列表:developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions

查看每个应用程序的权限

你可以使用设置应用程序或adb shell 命令查看每个应用程序的权限。

要使用设置应用程序,请转到设置 | 应用。选择一个应用程序,向下滚动以查看应用程序使用的权限。你可以在以下截图中看到 Lollipop 版本:

查看每个应用程序的权限

在 Android 棉花糖系统中,用户界面是不同的。

查看每个应用程序的权限

第二个选项是使用adb shell 命令与aapt命令:

  1. 列出所有应用程序及其安装路径。例如,让我们尝试使用以下命令找出 Facebook 群组应用程序的权限:

    adb shell pm list packages –f
    
    

    我们可以使用-3标志仅显示第三方应用程序,而不是整个列表。

  2. 一旦我们获取到包位置(apk),我们需要通过adb pull 从设备中提取它:

    adb pull /data/app/com.facebook.groups-1/base.apk
    
    
  3. 我们最后一步显示权限是使用aapt,在你的特定构建工具版本的build-tools文件夹中可以找到:

    aapt d permissions base.apk
    
    

    这给我们以下截图作为结果:

    查看每个应用程序的权限

    要查看整个设备的权限,请查看以下截图:

    查看每个应用程序的权限

使用adb命令,你可以打印出设备上所有已知的权限。adb命令中的包管理器(pm)命令如下所示:

$ adb shell pm list permissions [options] <GROUP>

列出权限获取[options]<GROUP>参数(都是可选的)。

在这里,options可以是以下内容:

  • -g:这指的是按组组织权限的列表

  • -f:这将打印出所有信息

  • -s:这将打印简短的摘要,这是用户在检查权限或批准权限时在屏幕上看到的内容

  • -d:这会查找并只打印出被认为危险的权限

  • -u:这仅列出用户可见的权限

理解 Android 棉花糖权限

Android Marshmallow 引入了一个新的应用权限模型,使用户在安装和/或升级应用时过程更加简单。在 Marshmallow 上运行的应用应该遵循新的权限模型,在该模型下,用户可以在安装后授予或撤销权限——权限只有在用户接受的情况下才会被授予。

新的权限模型支持向后兼容,这意味着你的应用仍然可以在运行旧版本 Android 的设备上安装和运行,并在这些设备上使用旧的权限模型。

概述

在 Android Marshmallow 版本中,引入了新的应用权限模型。

让我们更深入地了解一下:

  • 声明权限:应用需要的所有权限都在清单文件中声明,这样做是为了以类似早期 Android 平台版本的方式保持向后兼容性。

  • 权限组:如前所述,权限根据其功能被划分为权限组:

    • PROTECTION_NORMAL 权限:有些权限在用户安装应用时授予。在安装过程中,系统会检查应用的清单文件,并自动授予匹配PROTECTION_NORMAL组的权限。

    • INTERNET 权限:一个重要的权限是INTERNET权限,该权限在安装时授予,用户无法撤销。

  • 应用签名权限已授予:在安装时,系统不会提示用户授予任何权限。

  • 用户在运行时授予的权限:作为应用开发者,你需要在自己的应用中请求权限;系统会向用户显示一个对话框,用户的回应会传递回你的应用,通知权限是否被授予。

  • 权限可以被撤销:用户可以撤销之前授予的权限。我们必须学会如何处理这些情况,稍后会详细介绍。

注意

如果应用的目标是 Android Marshmallow 版本,则必须使用新的权限模型。

权限组

在处理权限时,我们将它们划分为不同的组。这种划分是为了在使用户审查和批准权限时能够快速交互。每个权限组只需授权一次。如果你添加了新的权限或请求同一权限组内的权限,而用户已经批准了该组,系统将自动授予你新增的权限,而无需用户再次审批。

欲了解更多信息,请访问developer.android.com/reference/android/content/pm/PermissionInfo.html#constants

用户安装应用时,只有那些在清单文件中列出的属于PROTECTION_NORMAL组的权限会被授予。

如果应用程序使用与声明权限的应用程序相同的证书签名,那么来自PROTECTION_SIGNATURE组的权限请求将被授予。

注意

应用程序在运行时不能请求签名权限。

系统组件自动接收其清单中列出的所有权限。

运行时权限

Android Marshmallow 展示了一个新的权限模型,用户可以直接在应用程序运行时管理应用权限。谷歌改变了旧的权限模型,主要是为了使用户和应用程序开发者的安装和自动更新更加容易和无摩擦。这使得用户无需预先批准应用程序需要的每个权限即可安装应用程序。用户可以在不检查每个权限并因为一个权限而拒绝安装的情况下安装应用程序。

用户可以为已安装的应用程序授予或撤销权限,将调整和选择的自由留在用户手中。

大多数应用程序在将目标 API 更新到 23 时需要解决这些问题。

考虑编码权限

好了,经过所有解释之后,我们来到了编码部分,这里我们将亲自动手编码。以下是用于处理权限的关键方法:

  • Context.checkSelfPermission(): 这检查你的应用程序是否被授予了权限

  • Activity.requestPermission(): 这在运行时请求一个权限

即使你的应用程序还没有针对 Android Marshmallow,你也应该测试你的应用程序并准备支持它。

测试权限

在 Android Marshmallow 的权限模型中,你的应用程序必须在运行时向用户请求单个权限。对于遗留应用程序有有限的兼容性支持,你应该测试你的应用程序,并确保它得到支持。

你可以使用以下测试指南,用新的行为进行应用程序测试:

  • 映射你应用程序的权限

  • 测试权限被授予和撤销的流程

adb命令行可以非常有助于检查权限:

  • 可以使用以下adb命令按组列出应用程序的权限和状态:

    adb shell pm list permissions -g
    
    
  • 你可以使用以下adb语法授予或撤销权限:

    adb shell pm [grant|revoke] <permission.name>
    
    
  • 你可以使用以下adb命令授予权限并安装apk

    adb install -g <path_to_apk>
    
    

为运行时权限进行编码

当我们想要调整应用程序以适应新模型时,我们需要确保我们组织好步骤,不要遗漏任何权限:

  • 检查应用程序运行的平台:当我们运行对 API 级别敏感的代码片段时,我们从检查我们正在运行的版本/API 级别开始。

    到现在,你应该熟悉Build.VERSION.SDK_INT

  • 检查应用程序是否具有所需的权限:在这里,我们得到了一个全新的 API 调用:

    Context.checkSelfPermission(String permission_name)

    通过这种方式,我们默默地检查权限是否被授予。

    这个方法立即返回,因此任何与权限相关的控制/流程都应该首先通过检查这个方法来处理。

  • 请求权限:我们有一个新的 API 调用,Activity.requestPermissions (String[] permissions, int requestCode)。这个调用会触发系统显示请求权限的对话框。这个方法是异步工作的。

    你可以一次性请求多个权限。第二个参数是一个简单的请求代码,在回调中返回,以便你可以识别这些调用。这就像多年来我们处理 startActivityForResult()onActivityResult() 一样。

    另一个新的 API 是 Activity.shouldShowRequestPermissionRationale(String permission)

    当你请求权限并且用户拒绝请求时,此方法返回 true。在确认后,向用户解释为什么需要这个权限被认为是最佳实践。用户可以选择拒绝权限请求并选择不再询问选项;然后,此方法将返回 false

下面的示例代码检查应用是否有读取用户联系人的权限。如果需要,它会请求权限,结果回调返回到 onRequestPermissionsResult

if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
  requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, SAMPLE_MATRIXY_READ_CONTACTS);
}
//Now this is our callback
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
  switch (requestCode) {
  case SAMPLE_MATRIXY_READ_CONTACTS:
    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
      // permission granted - we can continue the feature flow.
    } else {
      // permission denied! - we should disable the functionality that depends on this permission.
    }
  }
}

为了确保我们都知道使用的常量,以下是解释:

  • public static final int PERMISSION_DENIED=-1

    自 API 级别 1 起,给定的包未获得权限

  • public static final int PERMISSION_GRANTED=0

    自 API 级别 1 起,给定的包已获得权限。

如果用户拒绝你的权限请求,你的应用应该采取适当的行动,比如通知用户为什么需要这个权限,或者解释没有这个权限功能无法工作。

注意

你的应用不能假设用户已经进行了交互,因为用户可以选择拒绝授予权限并选择不再显示选项;你的权限请求会被自动拒绝,onRequestPermissionsResult会收到结果。

最佳实践和用法说明

新的权限模型为用户带来了更流畅的体验,同时也为开发者带来了一些额外的代码处理。它使得安装和更新应用更加容易,让用户对应用的操作感到舒适。

极简主义是一个很好的选择

不要贪求过多的权限!在我们的应用生命周期中,应尽量减少权限请求。请求大量权限并维护它们可能会让一些用户感到危险,我们应当尽可能减少权限请求,让功能运行顺畅,以便用户能够轻松无忧地使用。在可能的情况下,考虑使用意图——依赖其他应用为我们完成一些工作(减少权限意味着减少摩擦,将好的应用变成卓越的应用)。

一次性请求过多权限

用户可能会因为太多弹出的对话框而分心,这些对话框要求他们授予越来越多的权限。相反,你应该在需要时请求权限。

然而,每条规则都有一些例外。你的应用可能一开始就需要一些权限,比如相机应用一开始就显示相机权限。但是,将照片设置到联系人可以在用户触发该特定操作时完成和请求。尝试映射你的流程,让用户更容易理解正在发生的事情。如果用户通过你的应用请求将信息设置到联系人,他们将理解你请求了联系人的权限。

再提供一个建议:带教程的应用可以在教程中整合必要的权限请求,让用户更好地理解流程以及为什么需要每个权限。

诚实可能是一项极好的政策

当请求权限时,系统会显示一个对话框,告知用户你的应用需要哪个权限,但不会说明原因。考虑一下那些不喜欢被蒙在鼓里,想知道为什么现在需要这个权限的用户,或者因为猜测而拒绝权限的用户。情况可能更糟:有时,用户的鼠标可能离一星评价或卸载按钮只有 2 厘米远。

这就是为什么在调用requestPermissions()之前解释你的应用为什么需要权限是个好主意。

请记住,大多数开发者会选择一个教程,但很多用户可能会选择尽可能跳过教程,所以你必须确保除了教程中的内容外,你还能提供关于权限的信息。

需要支持处理运行时权限吗?

使用最新修订的v4v13支持库(23,与 Android Marshmallow API 版本相同,因此容易记住)管理权限更容易。

支持库现在提供了几种新方法来管理权限,并且可以在任何可以使用这些库的设备上正常工作。例如,这为你节省了检查足够 API 级别的时间,无论设备是否运行 Android Marshmallow。如果应用安装在运行 Android Marshmallow 的设备上,就能实现正确的行为——就像运行相同的框架调用一样。即使在较低版本上运行,你也可以从支持库方法中得到预期的行为。

v4 支持库有以下方法:

  • ActivityCompat.checkSelfPermission (Context context, String permission)

    这会检查你的应用是否拥有一个权限。如果应用有该权限,则返回PERMISSION_GRANTED;否则,返回PERMISSION_DENIED

  • ActivityCompat.requestPermissions (Activity activity, String[] permissions, int requestCode

    如果需要,这会请求权限。如果设备没有运行 Android 6.0,你会得到一个回调。

  • ActivityCompat.OnRequestPermissionsResultCallback(int requestCode, String[] permissions, int[] grantResults)

    如果应用已经拥有指定权限,这将传递PERMISSION_GRANTED;如果没有,则传递PERMISSION_DENIED

  • ActivityCompat.shouldShowRequestPermissionRationale (Activity activity, String permission)

    如果用户至少拒绝了一次权限请求并且还没有选择不再询问选项,这将返回true

根据设计模式,我们现在应该向用户提供更多关于此功能的信息以及为什么这些权限对应用如此重要。

注意

如果设备未运行 Android 棉花糖版本,shouldShowRequestPermissionRationale将始终返回false

PermissionChecker类也包含在 v4 中。

这个类为使用 IPC 的应用提供了几种方法,在 IPC 调用时检查特定包是否具有指定权限。

安卓有一个兼容模式,允许用户为遗留应用撤销对受权限保护方法的访问。当用户在兼容模式下撤销访问时,应用的权限保持不变,但对 API 的访问受到限制。

PermissionChecker方法可以在正常和遗留模式下验证应用权限。

注意

如果你的应用代表其他应用作为中介,并且需要调用需要运行时权限的平台方法,则应使用适当的PermissionChecker方法,以确保其他应用被允许执行该操作。

v13 支持库提供了以下权限方法:

  • FragmentCompat.requestPermissions()

    如果需要,这将请求权限。如果设备未运行 Android 6.0,你将得到一个回调。

  • FragmentCompat.OnRequestPermissionsResultCallback

    如果应用已经拥有指定权限,这将传递PERMISSION_GRANTED;如果没有,则传递PERMISSION_DENIED

  • FragmentCompat.shouldShowRequestPermissionRationale()

    如果用户至少拒绝了一次权限请求并且还没有选择不再询问选项,这将返回true

根据设计模式,我们现在应该向用户提供更多关于此功能的信息以及为什么这些权限对应用如此重要。

注意

如果设备未运行 Android 棉花糖版本,它将始终返回false

你可以查看示例项目,了解处理权限的三种方法:

github.com/MaTriXy/PermissionMigrationGuide

要了解有关权限设计模式的更多信息,请阅读谷歌的模式 - 权限,链接为www.google.com/design/spec/patterns/permissions.html

一些权限是正常且更安全的用法

安卓系统会根据权限的保护级别来标记权限。级别描述可以在developer.android.com/reference/android/content/pm/PermissionInfo.html找到。

我们讨论的相关级别是PROTECTION_NORMAL,当应用程序拥有这些权限时,被认为是几乎没有风险的。

假设你想构建一个手电筒应用;允许你的应用打开闪光灯被认为对隐私或安全没有巨大风险,这就是为什么手电筒权限被标记为PROTECTION_NORMAL

当你在清单中声明普通权限时,系统会在安装时自动授予这些权限。对于普通权限组,不会有授权提示,且用户无法撤销这些权限。

这意味着你可以确信,普通权限在安装时会被授予。

目前,被归类为PROTECTION_NORMAL的权限如下:

  • android.permission.ACCESS_LOCATION_EXTRA_COMMANDS

  • android.permission.ACCESS_NETWORK_STATE

  • android.permission.ACCESS_WIFI_STATE

  • android.permission.ACCESS_WIMAX_STATE

  • android.permission.BLUETOOTH

  • android.permission.BLUETOOTH_ADMIN

  • android.permission.BROADCAST_STICKY

  • android.permission.CHANGE_NETWORK_STATE

  • android.permission.CHANGE_WIFI_MULTICAST_STATE

  • android.permission.CHANGE_WIFI_STATE

  • android.permission.DISABLE_KEYGUARD

  • android.permission.EXPAND_STATUS_BAR

  • android.permission.FLASHLIGHT

  • android.permission.GET_ACCOUNTS

  • android.permission.GET_PACKAGE_SIZE

  • android.permission.INTERNET

  • android.permission.KILL_BACKGROUND_PROCESSES

  • android.permission.MODIFY_AUDIO_SETTINGS

  • android.permission.NFC

  • android.permission.PERSISTENT_ACTIVITY

  • android.permission.READ_SYNC_SETTINGS

  • android.permission.READ_SYNC_STATS

  • android.permission.READ_USER_DICTIONARY

  • android.permission.RECEIVE_BOOT_COMPLETED

  • android.permission.REORDER_TASKS

  • android.permission.SET_TIME_ZONE

  • android.permission.SET_WALLPAPER

  • android.permission.SET_WALLPAPER_HINTS

  • android.permission.SUBSCRIBED_FEEDS_READ

  • android.permission.TRANSMIT_IR

  • android.permission.VIBRATE

  • android.permission.WAKE_LOCK

  • android.permission.WRITE_SETTINGS

  • android.permission.WRITE_SYNC_SETTINGS

  • android.permission.WRITE_USER_DICTIONARY

  • com.android.alarm.permission.SET_ALARM

  • com.android.launcher.permission.INSTALL_SHORTCUT

总结

正如你所见,Android 权限系统和模型非常广泛,引入了一些变化,可以帮助应用开发者和应用程序获得更多的牵引力和安装量,并让用户决定你的应用程序何时能够使用每个依赖权限的功能。但请记住,这只是一个起点,Android Marshmallow 还需要获得市场份额并得到 OEM 的采用,让用户拥有选择的自由。作为应用开发者,你必须提前做好准备,确保你的应用开发面向未来,让新用户尽快享受最新的更新,同时保持你的应用高性能。

在下一章中,我们将详细介绍 Android Marshmallow 版本中的一个虽小但重要的特性:应用链接。

第二章:应用链接

新版 Android Marshmallow 的一个主要改进是强大的应用链接功能。它允许将你的应用与你所拥有的网络域关联起来。通过这种关联,你作为开发者允许系统确定处理特定网络链接的默认应用,并跳过提示用户选择应用的步骤。减少点击次数意味着摩擦更少,这意味着你可以更快地访问内容;这使用户和开发者都感到满意。在本章中,我们将涵盖以下主题:

  • Android Intent 系统

  • 创建网站关联

  • 触发应用链接验证

  • 应用链接设置与管理

  • 测试应用链接

Android Intent 系统

几乎每个开发者都知道Android Intent系统是什么,但我们会稍作解释并概述理解应用链接功能所需的基本原则。Android Intent 系统可以在 Android 平台中找到;这允许通过小而简单的数据包传递数据。意图意味着我们想要执行一个动作。你可能已经知道了一些基本的意图:

  • startActivity()

  • startActivityForResult()

  • startService()

  • sendBroadcast()

下图展示了针对startActivity()onCreate()意图的 Android Intent 系统:

Android Intent 系统

来源:http://developer.android.com/guide/components/intents-filters.html

Android Intent 系统是一个灵活的机制,用于让应用能够处理内容和请求。多个应用可以在它们的意图过滤器中声明匹配的URI(即统一资源标识符的简称)模式。当用户点击没有默认启动处理程序的网络链接时,平台可能会显示一个对话框,让用户从声明了匹配意图过滤器的应用列表中选择。

意图还用于在系统范围内触发动作,其中一些动作是系统定义的,比如ACTION_SEND(称为分享动作),你作为应用开发者可以将特定的信息分享/发送到另一个应用程序,以完成用户所需的一个动作。

在 Android Marshmallow 之前,浏览器处理网页上点击的每个链接,系统会检查是否有可用的自定义 URI 方案。你的应用程序可以通过自定义 URI 方案处理特定的自定义动作。这种方法有时比较复杂,并不支持处理整个网络域下的链接。现在,这成为了可能。Android Marshmallow 增加的对应用链接的支持允许你作为应用开发者,将应用与一个网络域关联起来。这样,系统会自动确定哪个应用是处理特定网络链接的默认应用,而不是显示让用户选择应用的处理对话框。

注意

如果你希望了解更多关于意图的信息,可以访问以下链接:

developer.android.com/guide/components/intents-filters.html

创建网站关联

作为应用开发者以及网站所有者,你需要声明一个网站与应用的关联。声明是通过托管一个名为 assetlinks.json 的 JSON 文件来完成的。该文件必须位于有关域名的特定位置,例如:

https://<domain>:<optional port>/.well-known/assetlinks.json

注意

此文件通过 HTTPS 协议访问和验证,而不是 HTTP。

为什么需要这个文件?

JSON 文件保存有关将成为此域名下 URL 默认处理程序的 Android 应用程序的信息。在 JSON 文件中,你必须具有以下结构:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourapp.androidapp",
    "sha256_cert_fingerprints": [""]
  }
}]

以下是一些前面结构中的元素:

  • package_name:这是来自你应用清单中的包名。

  • sha256_cert_fingerprints:这是你应用的 SHA-256 指纹

    如果你没有这个 SHA安全哈希算法的简称),请使用以下命令:

    keytool -list -v -keystore app_release_signing.keystore
    
    

触发应用链接验证

你可以请求自动验证 assetlinks.json 文件中声明的任何应用链接。请求验证是通过将 android:autoVerify 属性添加到清单中的每个意图过滤器并设置为 true 来完成的。

假设我们拥有一个 WhatsApp 应用程序和域名。我们希望自动验证具有 android.intent.action.VIEW 动作的意图过滤器。

以下是一个来自 WhatsApp 的示例活动,它处理应用链接和自动验证属性:

<activity android:name="com.whatsapp.XXX" …>
  <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="http" android:host="www.whatsapp.com"/>
    <data android:scheme="https" android:host="www.whatsapp.com"/>
  </intent-filter>
</activity>

android:autoVerify 属性提醒平台在安装应用时验证应用链接。如果应用链接的验证失败,你的应用将不会被设置为处理这些链接的首选应用。如果用户打开其中任何一个链接时没有首选应用来处理这些链接,则会显示选择应用对话框。

如果用户已经使用系统设置并将一个应用设置为首选应用,那么链接将直接进入该应用,但这不是因为验证成功。

应用链接设置和管理

为了便于管理,你可以进入系统设置,通过导航到 设置 | 应用 | 应用信息 | 默认打开 来调整 URL 处理。

应用链接设置和管理

测试应用链接

与我们添加的每个新功能一样,我们必须测试我们将添加到应用程序中的应用链接功能。

检查清单和列出域名

我们的第一步是检查清单,确保所有域名都正确注册,并且所有意图过滤器都定义良好。下面提到的所有条件下的链接/域名是我们需要测试的:

  • 带有 HTTP 或 HTTPS 值的 android:scheme 属性

  • 带有域名 URI 模式的 android:host 属性

  • category 元素,可以是以下之一:

    • android.intent.action.VIEW

    • android.intent.category.BROWSABLE

数字资产链接 API

我们可以使用数字资产链接 API,通过以下语法确认我们的链接 JSON 文件是否被适当地托管和定义:

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://<DOMAIN>:<port>& relation=delegate_permission/common.handle_all_urls

测试我们的意图

既然我们已经确认托管的 JSON 文件是有效的,我们将在设备上安装应用,并等待至少 20-30 秒,让验证过程完成。之后,我们可以使用以下语法检查系统是否验证了我们的应用并设置了正确的链接处理策略:

adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "http://<DOMAIN>:<port>"

例如,如果我们使用 YouTube 视频,可以使用以下命令触发 YouTube 应用直接打开视频:

adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "http://youtu.be/U9tw5ypqEN0"

使用 adb 检查策略

Android 调试桥adb)可以帮助我们使用以下命令检查设备上所有应用的现有链接处理策略:

adb shell dumpsys package domain-preferred-apps

下面的截图是前面命令的结果:

使用 adb 检查策略

另一个选项如下:

adb shell dumpsys package d

下面的截图是前面命令的结果:

使用 adb 检查策略

注意

安装后,我们必须等待至少 20-30 秒,以便系统完成验证过程。

下面的列表指示了每个用户与应用关联的域:

  • :这指的是应用在其清单中声明的包名。

  • :这指的是由该应用处理的网络链接的主机列表;空格用作分隔符。

  • 状态:这指的是此应用的当前链接处理设置。

通过验证和android:autoVerify="true"将显示状态为always

状态后面的十六进制数字(如前一个截图所示)是 Android 系统记录的用户应用链接偏好的记录。它并不表示验证已经成功。

注意

用户可以在验证过程结束前更改应用链接设置,这意味着我们可能会看到一个验证成功的假阳性。用户偏好优先于程序化验证,因此我们会看到链接直接跳转到我们的应用,而不会显示对话框,就像验证已经成功一样。

总结

正如我们所见,应用链接在 Android Marshmallow 中变得非常强大。这允许作为应用开发者的你帮助系统更好地决定如何行动。处理网页 URL 将为你提供更广泛的曝光,为你的应用带来更大的漏斗和更好的体验,从而你可以为用户提供更好的服务(这反过来又会导致更好的评分、更多的下载,反之亦然)。

应用链接的实现简单易懂,在当今的移动/网络世界中是必不可少的特性。尽管应用链接能为使用你应用程序的用户提供更好的操作处理,但用户可能会有多个设备,期待在每个设备上都有相同的行为表现,如果他们的数据和处理动作能够顺畅进行,用户将更加投入。这引出了我们的下一章内容,将会教您如何备份用户设置及更多功能。

第三章:应用的自动备份

你是否曾经花时间在手机上设置一个应用,使用一段时间后,投入大量内容,由于意外而更换手机,却发现你的数据和设置随风而去了?

Android Marshmallow 的一个关键特性是它支持为用户应用进行完全自动的数据备份和恢复。这提升了用户体验,使整体参与更加有趣,并缩短了多设备上的登机时间。正如我们在前几章中讨论的,快乐的用户造就快乐的开发者。

你可以卸下设置新设备的负担;无论是新增设备还是替换设备都没关系。用户最终将拥有相同的应用配置和数据,使得工作更加设备无关。要在你的应用上启用此功能,你必须针对 Android Marshmallow SDK 的版本 23;默认情况下不需要额外的代码,尽管你可以根据需要配置此功能并允许特定的行为。当用户更换或升级设备时,数据会自动恢复。

在本章中,我们将学习此功能的工作原理以及配置我们想要备份的信息。我们将涵盖以下主题:

  • 概述

  • 数据备份配置

  • 备份配置测试

  • 重要的信息

概述

自动备份功能是通过将应用内创建的数据上传到用户的 Google Drive 账户来创建的,并保持加密状态。这不会影响用户的驱动器配额或你的配额。每个应用对每个用户限制为 25 MB 的备份,达到这个量后,你的应用将停止备份。另外,请注意这是完全免费的

备份是在夜间以 24 小时为周期进行的,通常是自动的,当设备处于空闲、充电并连接到 Wi-Fi 网络时。这些条件是为了提高电池效率、数据费用,当然也是为了将用户干扰降到最低。Android 系统有一个备份管理器服务,它将所有可用的备份数据上传到云端。更换新设备或卸载后重新安装应用将触发恢复操作,进而将数据复制到应用的数据目录中。

注意

这种新行为允许你继续使用现有的备份服务调用。

要了解更多关于在 Android Marshmallow 之前使用的Android 备份服务,请访问:

developer.android.com/guide/topics/data/backup.html

数据备份配置

我们有很多想要为用户备份的数据,但我们也不希望备份所有数据。假设我们都同意不备份用户的密码或其他敏感数据,但如果你有一个基于用户使用的设备生成的特定应用配置该怎么办?这也应该以类似于设备令牌(如Google Cloud Messaging (GCM)等)的方式排除。我建议你弄清楚你的应用保留了哪些持久性数据,以及这些数据是否应该是与设备无关的。

你可以配置除了前面提到的自动排除文件之外需要备份的内容。这个配置应该在你的应用清单中通过android:fullBackupContent属性声明。你需要创建一个新的 XML 文件,它应该位于你的res/xml文件夹中,这将具有特定于你的应用数据备份的规则。

包含或排除数据

XML 文件配置包括一组简单的include/exclude标签,这些标签指示你是否需要备份一个目录或特定文件。请记住,默认情况下,XML 是减法的,这意味着除非你的 XML 中有排除的指示,否则备份所有可能的内容。

另一种可能的配置是建设性配置,你只需指定想要备份的内容,它们就会被添加到备份中。这种配置行为是通过在你的 XML 中添加一个include标签来实现的,从那时起,它将保持建设性。

正如我们示例中所看到的,我们在应用清单中指定了一个备份方案配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest   package="com.yourapp.androidapp">
  <uses-sdk android:minSdkVersion="16" />
  <uses-sdk android:targetSdkVersion="23" />
  <app android:fullBackupContent="@xml/androidapp_backup_config">
  </app>
</manifest>

在我们的清单文件中声明文件之后,我们还需要在res/xml文件夹中构建它;例如,请看以下内容:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
  <exclude domain="database" path="sensitive_database_name.db"/>
  <exclude domain="sharedpref" path="androidapp_shared_prefs_name"/>
  <exclude domain="file" path="some_file.file_Extension"/>
  <exclude domain="file" path="some_file.file_Extension"/>
</full-backup-content>

这个示例备份配置只排除特定数据被备份。所有其他文件都会被备份。

备份配置语法

尽管你应该已经整理出应用特定的持久数据,我们可以回顾一下应该出现在 XML 中的配置语法。配置 XML 文件的语法如下:

<full-backup-content>
  <include domain=[ "root" | "sharedpref" | "database" | "file" | "external"] path="string" />
  <exclude domain=[ "root" | "sharedpref" | "database" | "file" | "external"] path="string" />
</full-backup-content>

不要忘记阅读这里每个属性和元素的说明:

  • <include>:每当你想要特别将一个经过批准的资源添加到备份中时,你应该使用这个标签。记住,每当你指定一个<include>标签时,备份行为会变为建设性,系统只备份用<include>标签指定的资源。

  • <exclude>:每当你想要从备份中排除应用的任何资源时,你应该使用这个标签。如前所述,你应该排除敏感数据和应用的设备特定数据。这里的行为是这样的:系统备份你应用的所有数据,除了使用<exclude>标签指定的资源。

  • domain:这出现在include以及exclude标签上。它允许您声明您希望包括或排除在备份中的资源类型。域有特定的有效值供您选择:

    • root:这意味着资源应该在应用的root目录中

    • file:这意味着资源位于Files目录中的文件,可以通过getFilesDir()方法访问。

    • database:这意味着您的资源是一个数据库文件,可以通过getDatabasePath()方法或SQLiteOpenHelper类定位到该文件

    • sharedpref:这意味着您的资源是一个可以通过getSharedPreferences()方法访问的SharedPreferences对象

    • external:这意味着您的资源位于外部存储中的文件,该文件位于通过getExternalFilesDir()方法访问的目录中

    • path:这是一个到您希望包含或排除在备份中的资源的String路径

选择退出应用数据备份

在某些情况下,您可能会决定不希望在您的应用中使用应用数据备份功能。在这种情况下,您可以通知系统您的应用已经选择退出。

在您的清单中将android:allowBackup属性设置为false,可以通过以下命令完成:

android:allowBackup="false"

备份配置测试

到目前为止,您已经创建了一个备份配置,您可能(应该)测试它并确保您的应用保存数据,恢复数据,并且没有问题地运行。

设置备份日志

在测试应用配置之前,您可能想要启用日志记录;这是通过adb完成的,您将解析器log属性设置为VERBOSE

$ adb shell setprop log.tag.BackupXmlParserLogging VERBOSE

测试备份功能可以分为两部分:

  • 测试备份阶段

  • 测试恢复阶段

测试备份阶段

可以手动运行备份,但首先,您必须通过adb命令运行备份管理器:

$ adb shell bmgr run

在备份管理器启动并运行后,我们可以通过adb触发备份阶段,并将我们应用的包名作为<PACKAGE.NAME>参数运行:

$ adb shell bmgr fullbackup <PACKAGE.NAME>

测试恢复阶段

我们执行了备份阶段,一切顺利。现在,我们想要测试恢复阶段,并验证所有备份的数据是否正确恢复,我们没有遗漏任何资源。我们手动运行恢复(必须在备份应用数据后进行)。这是通过adb shell 完成的,指定您应用的包名为<PACKAGE.NAME>参数:

$ adb shell bmgr restore <PACKAGE.NAME>

注意

restore操作会停止您的应用并在实际执行恢复操作之前清除其数据。

故障排除

问题可能出现在任何地方,包括我们的案例中。如果您遇到问题,应该尝试通过导航到设置 | 备份和重置,打开和关闭备份来清除数据,对设备进行工厂重置:

故障排除

您可以使用此命令清除数据:

$ adb shell bmgr wipe <TRANSPORT> <PACKAGE.NAME>

<TRANSPORT>标签前缀为com.google.android.gms/。要查看传输列表,可以运行以下adb命令:

$ adb shell bmgr list transports

下面的截图是前面命令的结果:

故障排除

重要的字节

在我们进入下一章之前,让我们回顾一下 Android 应用备份功能中几个重要的子话题。

系统备份不包括以下内容:

  • 通过getCacheDir()方法(API 1 及以上版本)位于CacheDir中的文件

  • 通过getCodeCacheDir()方法(API 21 及以上版本)位于CodeCacheDir中的文件

  • 位于外部存储且不在ExternalFilesDir中,通过getExternalFilesDir(String type)方法获取的文件,其中类型可以是以下这些:

    • 文件目录根部的null

    • 以下类型用于特定子文件夹/目录:

      • android.os.Environment.DIRECTORY_MUSIC

      • android.os.Environment.DIRECTORY_PODCASTS

      • android.os.Environment.DIRECTORY_RINGTONES

      • android.os.Environment.DIRECTORY_ALARMS

      • android.os.Environment.DIRECTORY_NOTIFICATIONS

      • android.os.Environment.DIRECTORY_PICTURES

      • android.os.Environment.DIRECTORY_MOVIES

  • 通过getNoBackupFilesDir()方法(API 21 及以上版本)位于NoBackupFilesDir中的文件

需要从备份中排除的内容

尽管我们之前讨论过这个问题,你可能需要修改哪些应用数据有资格进行备份。

在排除的数据中,你必须排除任何由服务器发出或设备生成的特定于设备的标识符,包括 GCM 注册令牌。

你还必须为任何账户凭据或其他敏感信息添加排除逻辑。

备份代理和备份事件

你可以实现自己的BackupAgent属性,这允许你监听事件。BackupAgent有几个你可以重写的回调,其中一个是onRestoreFinished()方法,在成功恢复后调用。除了android:backupAgent,你应在清单中添加android:fullBackupOnly="true"属性,这将指示尽管你的应用有BackupAgent属性,但 Android Marshmallow 和其他设备将只执行全数据备份操作。

当你想要从SharedPreferences备份中排除几个键(如特定于设备的令牌、GCM 令牌等)时,这个技术会很有用。你不需要将 SharedPreferences 分割成多个文件,只需在恢复时,即在onRestoreFinished()被调用时移除这些键。

请记住,其他敏感数据不应该被备份。你可以阅读更多关于BackupAgent的信息:

Android 应用备份代理参考

总结

Android Marshmallow 为应用带来了出色的备份功能,减少了用户迁移到新设备时的摩擦。

在一个充满多样化应用的世界里,最大化自动备份的好处可以带来更好的用户体验。此功能的目的是减轻负担,并缩短用户在新设备上设置喜爱应用所需的时间。

允许用户在新安装后仅通过密码提示即可进入你的应用,这可以是一次很棒的经历;亲自尝试一下吧!你可以查看包含的示例代码,或者访问 GitHub 仓库:

应用自动备份示例

在我们下一章中,我们将深入探讨更多在 Android Marshmallow 上执行的变化,随着我们揭示其令人惊叹之处。

第四章:变化展开

安卓棉花糖有一些可能会被忽视的变化。这些变化虽短,但需要你全神贯注地理解它们,确保在使用已移除/不推荐使用的 API、新的流程或新的改进 API 时不会遗漏。

我整理了一系列变化,这些变化在为安卓 6.0(Marshmallow)构建应用时你可能需要了解或需要知道:

  • 节能模式

  • 采用可移动存储

  • 移除 Apache HTTP 客户端

  • 通知

  • 文本选择

  • 支持库通知

  • 安卓 Keystore 更改

  • Wi-Fi 和网络更改

  • 运行时

  • 硬件标识符

  • APK 验证

  • USB 连接

  • 直接分享

  • 语音交互

  • Assist API

  • 蓝牙 API 更改

前一组没有为重大变化单独设立章节,例如第一章中涵盖的权限模型,Android Marshmallow Permissions,或者改进的 API,比如我们将在下一章讨论的视频/音频/摄像头 API。

节能模式

安卓 6.0 增加了新的节能模式,DozeApp Standby,根据谷歌的测量,可将电池寿命延长多达 2 倍。Doze 模式旨在提高空闲设备的睡眠效率,而 App Standby 模式旨在防止应用在空闲状态下耗电。在这两种情况下,将设备插入充电器将允许恢复正常操作。

Doze 模式

当设备未插入电源,屏幕关闭且处于静止状态(可以通过加速度传感器等传感器确定)一段时间后,就会进入休眠状态。我们得到的是一个尽可能长时间保持系统在睡眠状态的状态。当安卓 6.0 设备处于 Doze 模式时,后台不会发生太多事情,如下图所示:

Doze 模式

简而言之,你认为会在后台发生的一切实际上并不会发生。

设备进入休眠状态时应用会发生什么?

当设备进入休眠状态时,你会遇到一些节能的系统行为,包括以下内容:

  • 除非你的应用接收到高优先级的 GCM,否则网络访问受限

  • 唤醒锁被忽略,但会被授予应用

  • 使用以下方式延迟同步和工作:

    • 同步适配器

    • JobScheduler(不允许运行;这是由系统强制执行的)

  • 闹钟延迟

    注意

    如果你有关键的闹钟并需要触发 UI:

  • 使用setAndAllowWhileIdle()方法

  • 不能滥用;每 15 分钟允许一次

  • Wi-Fi 扫描关闭

  • GPS 关闭

Doze 模式将在任何setAlarmClock()闹钟响起前不久结束;当静止和未插入状态互换时,它也可能结束。退出 Doze 模式将触发设备执行所有待定的工作和同步。

测试应用在 Doze 模式下的表现

使用你的设备(搭载 Android 6.0)和 adb 命令测试应用:

  1. 使用以下命令模拟未插电的设备:

    $ adb shell dumpsys battery unplug
    
    

    这将导致你的电池图标显示为设备未插电的状态。

  2. 使用以下命令进入下一个状态:

    $ adb shell dumpsys deviceidle step
    
    

    这可以在以下截图中看到:

    测试应用在 Doze 模式下

  3. 使用以下命令将电池状态重置回正常条件:

    $ adb shell dumpsys battery reset
    
    

你也可以使用以下命令列出可用的命令:

$ adb shell dumpsys deviceidle -h

这将打印出更多关于 deviceidle 使用的信息,如下面的截图所示:

测试应用在 Doze 模式下

App Standby 模式

当系统确定一个应用处于空闲状态时,应用待机模式是一种特殊的模式。除非应用表现出以下特点,否则在一段时间后会被视为空闲:

  • 在那时它有一个前台进程(一个活动或服务)

  • 它在锁屏或通知托盘中显示通知

  • 它是由用户明确启动的

  • 它通过设置应用被标记为不参与优化

应用在 App Standby 模式下会发生什么?

如果设备未插电,同步和任务会被推迟,网络访问也会受到限制。

如果设备插电,系统会释放待机状态下的应用锁,允许设备恢复网络访问和/或执行任何挂起的工作和同步。

注意

在长时间处于空闲状态时,系统允许空闲应用每天访问网络一次。

测试应用在 App Standby 模式下的表现

使用你的设备(搭载 Android 6.0)和 adb 命令测试应用:

  1. 模拟应用进入待机模式:

    $ adb shell am broadcast -a android.os.action.DISCHARGING
    $ adb shell am set-inactive <App Package Name > true
    
    
  2. 通过唤醒你的应用来模拟:

    $ adb shell am set-inactive <App Package Name > false
    
    
  3. 观察应用唤醒时会发生什么。测试从待机模式优雅地恢复。检查你的应用通知和后台任务是否如你所预期的那样工作。

你可以通过以下命令将你的应用设置为非活动状态:

$ adb shell am set-inactive <App Package Name > true

你也可以通过以下命令检查应用的状态:

$ adb shell am get-inactive <App Package Name >

注意

示例测试是对 Google Photos 行为进行的;保留所有权利。

控制台输出例如如下所示:

~ adb shell am set-inactive com.google.android.apps.photos false
~ adb shell am get-inactive com.google.android.apps.photos
Idle=false
~ adb shell am set-inactive com.google.android.apps.photos true
~ adb shell am get-inactive com.google.android.apps.photos
Idle=true

排除应用和设置

你可以通过设置应用排除应用参与 App Standby 模式,如前所述。执行此操作的步骤如下:

  1. 前往 设置 | 应用排除应用和设置

  2. 点击齿轮图标打开 配置应用 屏幕。排除应用和设置

  3. 选择 电池优化排除应用和设置

  4. 下面的截图显示了一个从 App Standby 模式中排除的应用列表——即那些未被优化的应用。你可以打开所有应用的选择,并为每个应用程序选择你需要的确切行为。排除应用和设置

提示

这里有一些你需要注意和记住的要点和提示:

  • 使用 PowerManager 实例的 isIgnoringBatteryOptimizations(),检查你的应用是否在 白名单

  • 使用以下方法直接引导用户到配置屏幕:

    startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS ));
    
  • 执行以下步骤以显示系统对话框,询问是否将特定应用添加到白名单:

    1. 在应用的清单中添加 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限。

    2. 创建一个指向你应用的 URI 包。

    3. 将 URI 包装在意图中,并像以下代码所示调用 startActivity()

      Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS , Uri.parse("package:" + getPackageName()));
      startActivity(intent);
      
  • 请注意,如果我们的应用已经被加入白名单,则不会再次显示对话框

可移动存储的采用

Android Marshmallow 允许用户 采用 外部存储设备,如 SD 卡。这类采用将格式化并加密存储设备,并将其作为内部存储挂载。完成后,用户可以在存储设备之间移动应用及其私有数据。系统的 android:installLocation 首选在清单中将用于确定每个应用可用的位置。你需要记住的是,使用 Context 方法获取目录或文件以及 ApplicationInfo 字段返回的值可能会在运行之间发生变化。你应始终动态调用这些 API。不要使用硬编码的文件路径或持久化完全合格的文件路径。

Context 方法如下:

  • getFilesDir()

  • getCacheDir()

  • getCodeCacheDir()

  • getDatabasePath()

  • getDir()

  • getNoBackupFilesDir()

  • getFileStreamPath()

  • getPackageCodePath()

  • getPackageResourcePath()

ApplicationInfo 字段如下:

  • dataDir

  • sourceDir

  • nativeLibraryDir

  • publicSourceDir

  • splitSourceDirs

  • splitPublicSourceDirs

你可以使用以下命令调试此功能,并启用通过 OTG(即 On The Go)线连接的 USB 驱动的采用:

$ adb shell sm set-force-adoptable true

有关 USB 的更多信息,请访问 Android USB 指南

Apache HTTP 客户端的移除

Apache HTTP 客户端已经被弃用有一段时间了——从 2011 年左右开始。在 Android 2.3 及更高版本上使用这个客户端并不推荐;现在随着 Android 6.0 Marshmallow 的发布,这个 API 已经被移除。因此,我们将使用 HttpURLConnection 类代替。

这个 API 更高效,减少了网络使用,并最小化了功耗。

如果你希望继续使用 Apache HTTP API,你必须在你的 build.gradle 文件中首先声明以下编译时依赖:

android {
  useLibrary 'org.apache.http.legacy'
}

注意

如果你在 Android Studio 中遇到编译错误,可以参考 stackoverflow 上的这些问题及其解决方案:

通知

通知功能有一些变化,如下:

  • Notification.setLatestEventInfo()方法现在已经移除。在构建通知时,我们必须使用Notification.Builder类。

  • 更新通知也是通过使用相同的Notification.Builder实例完成的,并调用build()方法将获得一个更新的Notification实例。如果需要支持旧版本,可以使用NotificationCompat.Builder,它包含在Android Support Library中。

  • adb shell dumpsys notification命令不再打印通知文本。现在的正确用法是adb shell dumpsys notification --noredact

  • 新增的INTERRUPTION_FILTER_ALARMS过滤器级别对应于新模式:仅闹钟不干扰

  • 新增的CATEGORY_REMINDER类别用于用户计划的提醒。

  • 新增的Icon类允许通过setSmallIcon()setLargeIcon()方法将图标附加到通知上。

  • 更新的addAction()方法现在接受一个Icon对象,而不是 drawable 资源 ID。

  • 新增的getActiveNotifications()方法允许你找出当前存活的通知。

  • 当我们使用以下方法时,可以获取用户希望在通知下看到和不希望看到的内容的一些知识:

    • 新增的getCurrentInterruptionFilter()方法返回当前允许通知打断用户的通知干扰过滤器。

    • 新增的getNotificationPolicy()方法返回当前的通知策略。

文本选择

材料设计指南规范部分讨论了应用程序中的文本选择。用户在你的应用内选择文本,现在你有一个 API 来集成类似于上下文操作栏的浮动工具栏设计模式。有关设计规范的更多信息,请访问www.google.com/design/spec/patterns/selection.html#selection-item-selection

实现步骤如下:

  1. 将你的ActionMode调用改为startActionMode(Callback,ActionMode.TYPE_FLOATING)

  2. 扩展ActionMode.Callback2

  3. 重写onGetContentRect()方法,并在视图中为内容Rect对象提供坐标。

  4. 当你需要使Rect对象和其位置不再有效时,调用invalidateContentRect()方法。

支持库通知

浮动工具栏不具备向后兼容性。Appcompat默认控制ActionMode对象。这将阻止浮动工具栏的显示。

实现步骤如下:

  1. 在返回的AppCompatDelegate对象上调用getDelegate()setHandleNativeActionModesEnabled()

  2. 将输入参数设置为false

此调用将ActionMode对象的控制权返回给框架,使得 6.0 设备支持ActionBar或浮动工具栏模式,并允许早期版本支持ActionBar模式。

Android Keystore 更改

从 Android 6.0 开始,Android Keystore 提供者不再支持数字签名算法DSA)。

有关 keystore 及其使用方式的更多信息,请访问developer.android.com/training/articles/keystore.html

Wi-Fi 和网络更改

Android Marshmallow 对 Wi-Fi 和网络 API 进行了一些更改。

更改WifiConfiguration对象的状态仅限于自行创建的对象。你被限制修改或删除用户或其他应用创建的WifiConfiguration对象。

在早期版本中,使用enableNetwork()强制设备连接到特定 Wi-Fi 网络并设置disableAllOthers=true会导致设备断开与其他网络的连接。这在 Android 6.0 中不会发生。当targetSdkVersion <=20时,你的应用将固定使用选定的 Wi-Fi 网络。当targetSdkVersion >=21时,你需要使用MultiNetwork API 并确保网络流量分配给正确的网络。有关MultiNetwork API 的更多信息,请参考developer.android.com/about/versions/android-5.0.html#Wireless

运行时

Android ART(即Android 运行时)在 Android Marshmallow 中也有所更新,以下是更新内容:

  • newInstance()方法:修复了Dalvik(另一个运行时)在检查访问规则时的问题。如果你想覆盖访问检查,请使用输入参数设置为truesetAccessible()方法。

  • 使用v7 Appcompat库或v7 Recyclerview库?你必须更新到最新版本。

  • 确保从 XML 引用的任何自定义类都已更新,使其类构造函数可访问。

  • 动态链接器的行为已更新。

  • ART 运行时区分库的soname和其路径;现在实现了通过soname的搜索。此问题有一个公开的 bug 已被修复;如果你想扩展阅读,请访问:

    code.google.com/p/android/issues/detail?id=6670

硬件标识符

Android 6.0 引入了一项重大更改,以增强数据保护;WifiInfo.getMacAddress()BluetoothAdapter.getAddress()方法现在返回一个常量值02:00:00:00:00:00,这意味着你不能依赖这些方法获取信息。

现在,当你尝试使用 API 中的某些方法时,你需要添加权限:

  • WifiManager.getScanResults()BluetoothLeScanner.startScan()需要授予这两个权限中的一个:

    • ACCESS_FINE_LOCATION权限

    • ACCESS_COARSE_LOCATION权限

  • BluetoothDevice.ACTION_FOUND:这必须具有ACCESS_COARSE_LOCATION权限

注意

当运行 Android 6.0(棉花糖)的设备启动后台 Wi-Fi 或蓝牙扫描时,外部设备看到的源地址是一个随机化的 MAC 地址。

APK 验证

平台现在对Android 软件包工具APKs)进行严格验证。

如果清单中声明的文件在 APK 本身中不存在,那么 APK 就被认为是损坏的。从 APK 中删除内容需要重新签名 APK。

USB 连接

默认情况下,USB 连接仅用于充电。用户现在必须授权通过 USB 端口进行交互。你的应用程序应考虑到这一点,并意识到可能不会获得授权。

直接分享

在我看来,技术最棒的事情之一是它为用户提供了很好的互动和受益选择。直接分享可以作为优点列表中的一个伟大补充,为整个应用世界带来极佳流畅的用户体验。那么,直接分享是什么呢?几乎现在每个应用都会使用某种信息/数据交换机制与用户设备上的其他应用或通过分享机制与外部世界进行交互。分享机制将一个应用程序的信息暴露给另一个应用程序。通常,用户会与几个亲密伙伴(家人、亲密朋友或同事)互动,这正是直接分享能提供帮助的地方。

直接分享是一组使分享直观快速的 API。你定义了直接分享目标,这些目标会在你的应用程序中启动特定的活动。这些目标显示在分享菜单中,允许更快地分享和流畅的数据流。

使用直接分享,用户可以将内容分享到目标,比如其他应用中的联系人。

实现步骤如下:

  1. 定义一个扩展了ChooserTargetService类的类。

  2. 在清单中声明你的服务。

  3. 指定BIND_CHOOSER_TARGET_SERVICE权限和意图过滤器SERVICE_INTERFACE动作。

一个服务声明的示例如下:

<service android:name=".MyChooserTargetService" android:label="@string/McTs_name" android:permission= "android.permission.BIND_CHOOSER_TARGET_SERVICE">
  <intent-filter>
    <action android:name= "android.service.chooser.ChooserTargetService"/>
  </intent-filter>
</service>

现在,我们已经声明了一个服务,对于每个我们想要公开的目标,我们在应用程序清单中添加了一个带有android.service.chooser.chooser_target_service名称的<meta-data>元素。

<activity android:name=".SampleDirectShareTarget" android:label="@string/SampleDirectShareTarget_name">
  <intent-filter>
    <action android:name="android.intent.action.SEND" />
  </intent-filter>
  <meta-data android:name= "android.service.chooser.chooser_target_service" android:value=".ChooserTargetService" />
</activity>

让我们看看我们服务中的代码:

public class MyChooserTargetService extends ChooserTargetService {
  private String mDirectShareTargetName;
  private final int MAX_SHARE_TARGETS = 5;

  @Override
  public void onCreate() {
    super.onCreate();
    mDirectShareTargetName = "Sharing Person demo #%d";
  }

  @Override
  public List < ChooserTarget > onGetChooserTargets(ComponentName sendTarget, IntentFilter matchedFilter) {
    ArrayList < ChooserTarget > result = new
    ArrayList < ChooserTarget > ();
    for (int i = 1; i <= MAX_SHARE_TARGETS; i++) {
      result.add(buildTarget(i));
    }
    return (result);
  }

  private ChooserTarget buildTarget(int targetId) {
    String title = String.format(mDirectShareTargetName, targetId);
    Icon icon = Icon.createWithResource(this, R.drawable.share_target_picture);
    float target_value = ((float)(25 - targetId) / 25);
    ComponentName componentName = new ComponentName(MyChooserTargetService.this, TargetActivity.class);
    Bundle bundle = new Bundle();
    bundle.putInt("simple_key", targetId);
    return (new ChooserTarget(title, icon, target_value, componentName, bundle));
  }
}

如果你希望更好地查看代码,可以访问代码片段;你可以访问gist.github.com/MaTriXy/adeacdf5496bcdae5f42

你必须实现onGetChooserTargets()方法,因为当触发直接分享时,会调用它。你返回一个表示应用程序分享入口点的ChooserTarget对象列表。onGetChooserTargets()的结果与常规ACTION_SEND活动本身一起包含。因此,我们只希望ChooserTarget对象能改善流程,而不是重复项。

在创建几个ChooserTarget对象时,每个对象可能都指向同一个活动。你必须确保额外的捆绑包将包含区分信息,以便每个请求都是唯一的。不要将自定义的Parcelable对象放在这个捆绑包中,因为它会导致崩溃。你可以通过developer.android.com/reference/android/service/chooser/ChooserTarget.html#ChooserTarget了解更多关于ChooserTarget的信息。

如果我们没有东西可以分享怎么办?

有时,对于特定的请求,你可能没有任何直接分享目标;那么,返回一个空列表会很棒。如果你知道在将来使用应用之前不会有任何结果,你也可以通过android:enabled="false"来禁用服务。另一个选择是只为 Android 6.0 启用服务。这可以通过使用布尔资源轻松完成:

  • 让我们添加一个名为is_share_targets_on的布尔资源:

    • 默认值是res/values/bools.xml;将其设置为false

    • Android 6.0 是 API 23,所以在res/values-v23/bools.xml中,将其设置为true

  • 在你的服务声明中添加android:enabled="@bool/is_share_targets_on"

直接分享最佳实践

以下是在直接分享中遵循的一些最佳实践:

  • Android 6.0 限制了分享目标的数量,只显示其中的八个。提供超过八个分享目标将根据分数显示最佳的八个。

  • 如果你的目标列表超过 1 MB,可能会出现FAILED BINDER TRANSACTION异常。

  • 尝试限制/封顶你从ChooserTargetService类中尝试返回的分享目标数量。

  • 确保你的应用图标显示正确,因为它将作为徽章应用于你用于直接分享目标的图标上。

语音交互

语音交互通常源于用户的语音动作。然而,语音交互活动在没有用户输入的情况下启动。Android 棉花糖有一个新的语音交互 API,结合语音动作,它允许我们将对话式语音体验构建到我们的应用中。使用isVoiceInteraction()方法来确定活动是否由语音动作触发。然后,你可以使用VoiceInteractor类与用户交互。

不要与isVoiceInteractionRoot()方法混淆,该方法仅在活动也是语音交互的根时返回true。在这里,如果你的活动直接由语音交互服务启动,而不是在语音交互过程中由另一个活动(另一个应用)启动,你将得到true

一个最佳实践是提示用户并确认这是他们预期的操作。你已经知道语音输入是从Google Now中触发的,你只需简单的语音输入,如open android.com,就可以打开 URL。现在,你可以发明新的语音动作并将它们注册到 Google,直接且具体地引导流量到你的应用。

要了解更多关于实现语音操作的信息,请访问developers.google.com/voice-actions/interaction/

辅助 API

2015 年的 Google I/O上,我们看到了Now on Tap功能,Google Now 能够窥视正在运行的应用并提供上下文辅助。Assist API 为用户通过助手进行互动提供了一种新方式。在使用助手之前,必须先启用它,使其能够了解当前上下文。通过长按主页按钮来触发助手,无论哪个应用处于活动状态都可以。

你可以通过设置WindowManager.LayoutParams.FLAG_SECURE标志来选择退出。

要加入,你需要使用新的AssistContent类。

为了能够从我们的应用向助手提供额外的上下文信息,我们需要遵循以下步骤:

  1. 实现Application.OnProvideAssistDataListener接口,当用户请求帮助时调用。

  2. 使用Application.registerOnProvideAssistDataListener()注册它。

  3. 重写onProvideAssistData()回调,当用户请求帮助时调用。它用于构建带有当前应用所有上下文的ACTION_ASSIST意图。

  4. 重写onProvideAssistContent()回调,这是可选的。当用户请求帮助时调用,允许我们提供与当前活动相关的内容引用。

  5. 完成后,使用Application.unregisterOnProvideAssistDataListener()注销自己。

蓝牙 API 变更

除了之前提到的变化,Android Marshmallow 6.0 还在蓝牙 API 中引入了一些其他变化。

蓝牙手写笔支持

手写笔已经存在一段时间了;在 Android 棉花糖版本之前,蓝牙手写笔没有完全支持规范。你可以将兼容的蓝牙手写笔与手机或平板配对并连接。由于你不仅限于屏幕上的触摸,还可以融合位置、压力和按钮状态数据,从而提供更精确的用户输入和体验。你的应用可以为手写笔按钮添加监听器并相应地作出反应。只需在你的活动中使用View.OnContextClickListenerGestureDetector.OnContextClickListener对象。

为了检测手写笔按钮的交互和移动,你需要以下内容:

  • MotionEvent方法

  • getTooltype()方法,如果带有按钮的手写笔在屏幕上触摸,将返回TOOL_TYPE_STYLUS

  • getButtonState()方法,在针对 Android 6.0 的应用中返回以下内容:

    • BUTTON_STYLUS_PRIMARY: 按下手写笔主按钮

    • BUTTON_STYLUS_SECONDARY: 按下副按钮

    • BUTTON_STYLUS_PRIMARY | BUTTON_STYLUS_SECONDARY: 同时按下这两个按钮

  • 针对低于 Android 6.0 API 级别的应用将导致以下结果:

    • BUTTON_SECONDARY: 按下手写笔主按钮

    • BUTTON_TERTIARY: 按下次级按钮

    • BUTTON_SECONDARY | BUTTON_TERTIARY:按下这两个按钮

改进的蓝牙低能耗扫描

你的应用里使用过蓝牙低能耗(BLE)吗?现在,扫描过程变得更加简单和高效了。使用新的 setCallbackType() 方法,并指定当系统发现/看到与 ScanFilter 类匹配的广告包时,你希望获得回调。与之前安卓版本相比,你将获得更高的电源效率。

概述

我们回顾了安卓棉花糖版本中的一些变化。所有这些变化都值得关注,并将在你的应用开发周期中为你提供帮助。在后续章节中,我们还会更详细地讨论一些其他变化。我们的下一章将讨论音频、视频和摄像头特性,以及在安卓 6.0.6 中做出的改变。

第五章:音频、视频和摄像头特性

Android Marshmallow 为我们提供了良好的音频、视频和摄像头功能,并且可以看到已经进行了改进,以启用和更好地支持新的或全新的协议,甚至改变一些 API 的行为,如摄像头服务。

在本章中,我们将尝试通过适当的讨论来解释这些变化及其使用方法和好处。在接下来的几页中,我们的旅程将涵盖以下主题:

  • 音频特性

  • 视频特性

  • 摄像头特性

音频特性

Android Marshmallow 6.0 对音频特性增加了一些增强功能,我们将在接下来的章节中进行介绍。

对 MIDI 协议的支持

android.media.midi包是在 Android 6.0(API 23)中新增的。

使用新的 midi API,现在可以比以前更简单的方式发送和接收MIDI(即Musical Instrument Digital Interface,乐器数字接口)事件。

该软件包旨在为我们提供以下能力:

  • 连接并使用 MIDI 键盘

  • 连接到其他 MIDI 控制器

  • 使用外部 MIDI 合成器、外部外围设备、灯光、展示控制等

  • 允许游戏或音乐创作应用动态生成音乐

  • 允许应用之间创建和传递 MIDI 消息

  • 允许 Android 设备在连接到笔记本电脑时作为多点触控控制器

在处理 MIDI 时,您必须在清单中声明,如下所示:

<uses-feature android:name="android.software.midi" android:required="true"/>

注意required部分;与其他特性类似,将其设置为true将使得只有当设备支持 MIDI API 时,您的应用才会在播放商店中可见。

您还可以在运行时检查 MIDI 支持,然后将 required 部分更改为false

PackageManager pkgMgr = context.getPackageManager();
if (pkgMgr.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
  //we can use MIDI API here as we know the device supports the MIDI API.
}

MidiManager

正确使用 MIDI API 的一种方式是通过MidiManager类;通过context获取它,并在需要时使用:

MidiManager midiMgr = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);

如需了解更多信息,您可以参考以下内容:

developer.android.com/reference/android/media/midi/package-summary.html

数字音频捕获和播放

为数字音频捕获和播放新增了两个类:

  • android.media.AudioRecord.Builder - 数字音频捕获

  • android.media.AudioTrack.Builder - 数字音频播放

这些将帮助配置音频源和接收器属性。

音频和输入设备

新增了hasMicrophone()方法到InputDevice类中。这将报告设备是否有开发者可用的内置麦克风。例如,您想要从连接到 Android TV 的控制器启用语音搜索,并在用户搜索时收到onSearchRequested()回调。然后,您可以验证在回调中获得的inputDevice对象是否有麦克风。

音频设备信息

新的AudioManager.getDevices(int flags)方法允许轻松检索当前连接到系统的所有音频设备。如果你想要在音频设备连接/断开时得到通知,可以通过AudioManager.registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler)方法注册你的应用到AudioDeviceCallback回调中。

AudioManager的变化

AudioManager类中引入了一些变化,如下所示:

  • 直接使用AudioManager来设置音量是不被支持的。

  • 使用AudioManager静音特定流是不被支持的。

  • AudioManager.setStreamSolo(int streamType, boolean state)方法已弃用。如果你需要独占音频播放,请使用AudioManager.requestAudioFocus(AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint)

  • AudioManager.setStreamMute(int streamType, boolean state)方法已弃用。如果你需要使用AudioManager.adjustStreamVolume(int streamType, int direction, int flags)来调整音量方向,你可以使用其中新增的常量之一。

  • ADJUST_MUTE将会静音。注意,如果该流已经静音,此操作无效。

  • ADJUST_UNMUTE将会取消静音。注意,如果该流未被静音,此操作无效。

视频特性

在 Android 棉花糖版本中,视频处理 API 已经升级,具备新的功能。一些新的方法甚至一个全新的类MediaSync只为开发者添加。

android.media.MediaSync

全新的MediaSync类被设计用来帮助我们同步渲染音频和视频流。你也可以使用它来仅播放音频或视频流。你可以使用动态播放速率,并通过回调返回非阻塞动作来输入缓冲区。关于正确使用方法,请阅读:

关于MediaSync的官方文档

MediaCodecInfo.CodecCapabilities.getMaxSupportedInstances 方法

现在,我们有了MediaCodecInfo.CodecCapabilities.getMaxSupportedInstances辅助方法,以获取支持的同时编解码器实例的最大数量。然而,我们只能将此视为上限。实际的同时实例数量可能会根据设备和在用时可用资源的数量而减少。

为什么我们需要知道这些?

假设我们有一个媒体播放应用,我们想在播放的电影之间添加效果。这将需要使用多个视频编解码器,解码两个视频,并将一个视频流重新编码以显示在屏幕上。通过检查这个 API,你可以添加更多依赖于多个编解码器实例的功能。

MediaPlayer.setPlaybackParams 方法

我们现在可以设置媒体播放速率,以实现快速或慢速动作播放。这将给我们一个机会,创建一个有趣的应用,其中我们可以放慢部分动作或快速播放,从而在播放时创建一个新视频。音频播放相应地同步,因此您可能会听到一个人慢慢地或快速地说话。

相机功能

在 Android Lollipop 中,有新的Camera2 API,现在在 Android Marshmallow 中,对相机、手电筒和图像重新处理功能有一些更多的更新。

手电筒 API

现在几乎每个设备都有相机,几乎每个相机设备都有闪光单元。添加了setTorchMode()方法来控制闪光手电筒模式。

setTorchMode()方法的使用方式如下:

CameraManager.setTorchMode (String cameraId, boolean enabled)

cameraId元素是您想要更改手电筒模式的闪光单元相机的唯一 ID。您可以使用getCameraIdList()获取相机列表,然后使用getCameraCharacteristics(String cameraId)检查该相机是否支持闪光。setTorchMode()方法允许您在不打开相机设备且不需要从相机请求权限的情况下打开或关闭它。一旦相机设备不可用,或者具有开启手电筒的其他相机资源不可用时,手电筒模式将会关闭。其他应用也可以使用闪光单元,因此您需要在需要时检查模式,或者通过registerTorchCallback()方法注册一个回调。

参考示例应用Torchi,查看完整代码:

github.com/MaTriXy/Torchi

注意

如果相机或其他相机资源正在使用中,开启手电筒模式可能会失败。

重新处理 API

如前所述,Camera2 API 得到了一些增强,以支持YUV和私有不透明格式图像的重新处理。在使用这个 API 之前,我们需要检查这些功能是否可用。这就是为什么我们要使用getCameraCharacteristics(String cameraId)方法,并检查REPROCESS_MAX_CAPTURE_STALL键的原因。

android.media.ImageWriter

这是一个在 Android 6.0 中新增的类。

它允许我们创建一个图像并将其输入到表面,然后返回到CameraDevice。通常,ImageWriterImageReader一起使用。

android.media.ImageReader

这是一个在 Android 6.0 中新增的类。

它允许我们直接访问在表面上渲染的图像数据。ImageReader加上ImageWriter允许我们的应用从相机创建图像流到表面,然后返回相机进行重新处理。

相机服务的改变

安卓棉花糖对先到先得的访问模型进行了更改;现在,服务访问模型有了偏好进程——那些被标记为高优先级的进程。这个变化导致我们开发人员需要处理更多逻辑相关的工作。我们需要确保考虑到这种情况:我们的优先级被提升(更高优先级)或被降级(由于应用程序的变化而降低优先级)。

让我们尝试用几个简单的要点来解释这个问题:

  • 当你想访问摄像头资源或打开及配置摄像头设备时,你的访问权限将根据应用程序进程的优先级进行验证。通常,具有前台活动(对用户可见)的应用程序进程会被赋予更高的优先级,这也使得在需要时获得所需访问权限的可能性更大。

  • 优先级的另一个极端,你会发现低优先级的应用程序可能会被搁置(从访问中撤销),当一个高优先级的应用程序尝试使用摄像头时。例如,当使用Camera API 时,你会在被踢出时收到onError()调用;当使用Camera2 API 时,你会在被踢出时收到onDisconnected()调用。

  • 野外一些设备能够允许不同的应用程序同时打开和使用不同的摄像头设备。摄像头服务现在可以检测并禁止由于多进程使用造成的性能问题。当服务检测到这类问题时,即使只有一个应用程序在使用该摄像头设备,它也会踢出低优先级的应用程序。

  • 在多用户环境中,切换用户时,为允许当前用户的应用程序正确使用和访问,将踢出之前用户配置文件中所有正在使用摄像头的应用程序。这意味着,切换用户将确保使用摄像头的应用程序停止使用摄像头。

总结

在本章中,我们介绍了安卓 API 中的一些变化和新增内容。安卓棉花糖主要是帮助我们这些开发人员实现更好的媒体支持,在使用音频、视频或摄像头 API 时展示我们的想法。

在下一章中,我们将介绍一些安卓特性,以了解在使用音频、视频或摄像头 API 时,安卓所做的特性增加和更改。

第六章:安卓工作

众所周知,安卓设备在全球市场占有巨大的市场份额,越来越多的企业采用BYOD(即自带设备)政策。这是借助Android for Work实现的,这是一个针对公司的特殊程序,其中安卓平台增加了多项功能,以便更好地管理移动设备,在公司内部进行管理和整合。

在处理企业甚至小型和中小型企业时,你需要遵循特定的指导方针,利用安卓 API 为你服务。你可以通过以下链接了解更多关于 Android for Work 的信息:

developer.android.com/training/enterprise/index.html

安卓棉花糖对 Android for Work 程序进行了一些更改,其中许多更改旨在为开发者和工作用户带来更好、更简单的使用体验。

在本章中,我们将介绍与 Android for Work 直接相关的安卓棉花糖的更改:

  • 行为变更

  • 单次使用设备改进

  • 静默安装/卸载应用

  • 改进的证书访问

  • 自动系统更新

  • 第三方证书安装

  • 数据使用统计

  • 管理运行时权限

  • VPN 访问和显示

  • 工作资料状态

行为变更

安卓棉花糖引入了一些与 Android for Work 相关的行为变更。

工作资料联系人显示选项

使用以下设置,你现在可以在拨号器通话记录中显示你的工作资料联系人:

DevicePolicyManager.setCrossProfileCallerIdDisabled(ComponentName admin, boolean disabled)

你还可以使用新的选项通过蓝牙显示工作联系人。将此设置为false将允许显示;默认值为true(禁用联系人共享选项):

DevicePolicyManager.setBluetoothContactSharingDisabled(ComponentNa me admin, boolean disabled)

Wi-Fi 配置选项

通过工作资料添加 Wi-Fi 网络时,通常添加的配置在资料被删除后仍然保持持久。现在,如果工作资料被删除,所有由资料所有者添加的配置都会被移除。

Wi-Fi 配置锁定

新增了一个Settings.Global设置:

WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN

此设置是一个整数值设置,这意味着零值或不存在将导致用户修改或删除所有 Wi-Fi 配置。将整数值设置为非零值将启动锁定,这意味着用户无法修改或删除设备所有者创建的 Wi-Fi 配置——用户创建的配置仍然可以修改。注意,活跃的设备所有者拥有任何 Wi-Fi 配置的完全权限,即使这些配置不是由他们创建的。

工作策略控制器添加

你可以继续向设备添加 Google 账户,但现在,在添加由工作策略控制器管理的账户时,流程已更改,以包括工作策略控制器的添加。添加的账户会提示用户安装适当的工作策略控制器。通过设置或通过启动设备设置向导添加账户时也是如此。有关如何构建工作策略控制器的更多信息,请阅读:

developer.android.com/training/enterprise/work-policy-ctrl.html

DevicePolicyManager的变化

DevicePolicyManager中,你可能会遇到一些行为上的变化;以下是与简短解释一起列出的一些变化:

  • setCameraDisabled()仅影响调用用户摄像头;如果配置文件是受管理的配置文件,那么该调用不会影响主用户上运行的摄像头应用。

  • setKeyguardDisabledFeatures()现在对配置文件所有者和设备所有者可用。

  • 配置文件所有者可以通过以下方式设置键盘锁限制:

    • KEYGUARD_DISABLE_TRUST_AGENTS:这将忽略在安全屏幕(PIN 码、图案或密码屏幕)上的键盘锁上的信任代理状态。

    • KEYGUARD_DISABLE_FINGERPRINT:这将禁用在安全屏幕(PIN 码、图案或密码屏幕)上的键盘锁上的指纹传感器。

    • KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS:这将允许在安全键盘锁屏幕上只显示编辑过的通知,并且只有由管理配置文件中的应用程序生成的通知。

  • createAndInitializeUser()现在已被弃用。

  • createUser()现在已被弃用。

  • 使用setScreenCaptureDisabled()方法,可以阻止Assist功能,但这仅在给定用户的应用程序在前台时发生。

  • EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM现在使用 SHA-256。对 SHA-1 的传统支持仍然存在,但根据文档,它将在未来的版本中被移除。

  • EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM现在仅使用 SHA-256。

  • 移除了EXTRA_PROVISIONING_RESET_PROTECTION_PARAMETERS,以防止 NFC 碰撞配置解锁一个受工厂重置保护的设备。

  • 在 NFC 配置过程中,可以通过EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE向设备所有者传递数据。

  • 新的DevicePolicyManager API 支持在 Android Marshmallow 新的权限模型下进行权限管理。你可以通过访问developer.android.com/reference/android/app/admin/DevicePolicyManager.html了解更多关于DevicePolicyManager的信息。

  • 如果用户取消了通过ACTION_PROVISION_MANAGED_PROFILEACTION_PROVISION_MANAGED_DEVICE意图启动的设置流程,现在将返回RESULT_CANCELED

  • Settings.Global发生了变化。

  • 通过setGlobalSettings()禁用以下设置:

    • BLUETOOTH_ON

    • DEVELOPMENT_SETTINGS_ENABLED

    • MODE_RINGER

    • NETWORK_PREFERENCE

    • WIFI_ON

  • 启用了通过setGlobalSettings()设置的WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN设置。

单次使用设备改进

作为设备所有者,你现在可以控制添加的设置,从而通过以下方式改善设备管理:

  • 可以使用setKeyguardDisabled()来禁用或重新启用键盘锁

  • 可以使用setStatusBarDisabled()来禁用或重新启用状态栏

  • UserManager.DISALLOW_SAFE_BOOT是一个新的常量,用于指示用户是否可以将设备启动到安全模式

  • Settings.Global.STAY_ON_WHILE_PLUGGED_IN将防止在连接电源时屏幕关闭

静默安装/卸载应用

现在,你可以使用PackageInstaller API 静默安装和卸载应用程序。这意味着可以在没有用户交互的情况下安装应用程序,甚至可以按照公司政策删除应用程序。此功能使你可以在不实际激活 Google 账户的情况下使用设备。Google Play for Work不是必需的,允许你将设备用作展示柜,展示尚未发布的特定应用等。

改进了证书访问

在 Android Marshmallow 之前,允许用户在无用户交互的情况下授予管理应用程序访问证书是不可能的,现在,已添加了一个新的回调:

DeviceAdminReceiver.onChoosePrivateKeyAlias (Context context, Intent intent, int uid, Uri uri, String alias)

此回调将允许设备所有者向请求的应用程序静默提供别名。

自动系统更新

在 Android 6.0 中添加了以下选项,其主要目的是允许设备所有者自动接受系统更新:

DevicePolicyManager.setSystemUpdatePolicy (ComponentName admin, SystemUpdatePolicy policy)

SystemUpdatePolicy也已添加,你可以从三个选项中选择:

  • TYPE_INSTALL_AUTOMATIC:一收到更新就更新

  • TYPE_INSTALL_WINDOWED:更新应该在定时系统维护期间完成,并且仅在那之后,持续 30 天,然后恢复正常行为

  • TYPE_POSTPONE:最多推迟 30 天的更新,之后恢复正常行为

如果你拥有如展示平板或展示柜模式的设备,此功能将非常有用,因为更新不应该干扰设备的工作。

第三方证书安装

第三方应用程序现在可以调用DevicePolicyManager API:

  • getInstalledCaCerts()

  • hasCaCertInstalled()

  • installCaCert()

  • uninstallCaCert()

  • uninstallAllUserCaCerts()

  • installKeyPair()

只有在设备所有者或配置文件所有者授权后,才能执行这些 API 调用。

数据使用统计

在 Android 6.0 中添加了一个新类:NetworkStatsManager。这可以帮助你查询可以在设置 | 数据使用中看到的数据使用统计信息。

配置文件所有者可以自动获得访问权限,以便查询其配置文件上的数据。设备所有者可以访问管理的主用户的 数据使用情况。

注意

android.app.usage.NetworkUsageStats类已重命名为NetworkStats

管理运行时权限

Android Marshmallow 引入了运行时权限模型,Android for Work 必须处理设备上的策略管理。作为设备所有者,你现在可以使用setPermissionPolicy()为所有应用程序的所有运行时请求设置策略。

你可以选择提示用户授予权限,或者自动静默地授予权限或拒绝权限。自动策略意味着用户不能在设置中修改应用的权限屏幕。

VPN 访问和显示

当你进入设置 | 更多 | VPN时,现在可以查看 VPN 应用。使用 VPN 时,显示的通知现在会具体到该 VPN 的配置方式:

  • 配置文件所有者:根据 VPN 配置和配置文件(个人、工作或两者都有)显示通知

  • 设备所有者:当 VPN 配置为整个设备时,会显示通知

工作配置文件状态

为了让用户知道他们处于不同的配置文件中,引入了两项新的指示:

  • 使用工作配置文件中的应用程序时,状态栏将显示一个公文包图标

  • 当直接从工作配置文件应用解锁设备时,会弹出一个提示,告知用户此应用运行在工作配置文件上。

概述

正如本章所看到的,Android Marshmallow 为 Android for Work 领域带来了许多变化。作为开发者,我们需要始终关注组织的实际需求。我们需要确保深入了解 Android for Work 的世界;Marshmallow 中的变化帮助我们构建和针对企业工作流程,同时得益于更简单的 API。

在下一章中,我们将学习 Chrome 自定义标签页 API 的使用和流程。

第七章:Chrome 自定义标签

你有没有想过在你的应用程序中添加一个WebView?也许你想要添加一些网页的浏览,并在你的应用程序中显示相关内容?我知道我必须这样做。几乎在每次,我都犹豫使用 WebView 功能,因为这是应用中最丑陋的部分之一。

你可以清楚地看到,WebView 功能是一个网页部分,UI 在相当多的 Android 版本之前就已经添加,这让我的 OCD UI/UX 感觉爆炸了。谷歌发布的最新功能之一就是Chrome 自定义标签

在本章中,我们将探讨 Chrome 自定义标签,并尝试解释和演示使用它而不是普通的旧 WebView 的好处:

  • 什么是 Chrome 自定义标签?

  • 何时使用 Chrome 自定义标签

  • 实现指南

什么是 Chrome 自定义标签?

好吧,我们大多数人都从每天的互联网浏览中知道标签。你使用哪个浏览器并不重要;所有浏览器都支持标签和多标签浏览。这允许我们同时打开多个网站,并在打开的实例之间导航。在 Android 中,事情差不多,但是使用 WebView 时,你没有标签。

什么是 WebView?

WebView 是 Android 操作系统的一部分,负责在大多数 Android 应用中渲染网页。如果你在 Android 应用中看到网页内容,很可能你正在看 WebView。这个规则的主要例外是一些 Android 浏览器,比如 Chrome、Firefox 等。

在 Android 4.3 及以下版本中,WebView 使用基于苹果Webkit的代码。在 Android 4.4 及更高版本中,WebView 基于Chromium项目,这是谷歌 Chrome 的开源基础。在 Android 5.0 中,WebView 被解耦成一个单独的应用程序,允许通过 Google Play 及时更新,而无需发布固件更新,与 Google Play 服务采用了同样的技术。

现在,让我们再次讨论一个简单场景:我们希望在我们的应用程序中显示网页内容(与 URL 相关)。我们有两个选择:要么启动浏览器,要么使用 WebView 构建自己的应用内浏览器。如果我们列出这两种选择,它们都有权衡或缺点。浏览器是一个外部应用程序,你实际上无法改变它的 UI;在使用它的过程中,你将用户推向其他应用,并且可能会在野外丢失他们。另一方面,使用 WebView 将用户紧密地留在应用内。然而,实际上处理 WebView 中所有可能的行为相当有负担。

谷歌听到了我们的抱怨,并推出了 Chrome 自定义标签来拯救。现在我们可以在应用程序中更好地控制网页内容,并且可以更干净、更美观地将网页内容嵌入到我们的应用中。

自定义选项

Chrome 自定义标签允许进行几项修改和调整:

  • 工具栏颜色

  • 进入和退出动画

  • 工具栏和溢出菜单的自定义操作

  • 预启动和预取内容以加快加载速度

何时使用 Chrome 自定义标签

自从 WebView 推出以来,应用程序一直在以多种方式使用它,嵌入内容——在 APK 内的本地静态内容以及动态内容,加载最初并非为移动设备设计的网页。后来,我们见证了移动网络时代的兴起,包括混合应用程序的出现。

Chrome 自定义标签页不仅仅是加载本地内容或移动兼容的网页内容。当你加载网页数据并希望允许简单实现、更容易的代码维护,以及进一步将网页内容变为应用程序的一部分时,应该使用它——就像它一直存在于你的应用中一样。

你应该使用自定义标签页的原因包括以下:

  • 容易实现:在需要时使用支持库,或者只需向你的View意图中添加额外内容。就这么简单。

  • 在应用用户界面修改中,你可以执行以下操作:

    • 设置工具栏颜色

    • 添加/更改操作按钮

    • 向溢出菜单中添加自定义菜单项

    • 设置并创建自定义的进入/退出标签页时的动画效果,回到上一个位置

  • 更简单的导航和导航逻辑:如果需要,你可以获得一个回调通知,了解外部导航的情况。你知道用户何时导航到网页内容,以及他们完成后应该返回哪里。

  • Chrome 自定义标签页允许你使用以下性能优化:

    • 你可以保持引擎运行,这么说吧,实际上给自定义标签页一个提前启动的机会,在使用它之前进行一些预热。这样做不会干扰或占用宝贵的应用程序资源。

    • 你可以提供一个 URL,在等待其他用户交互时在后台提前加载。这加快了用户可见的页面加载时间,并给用户一种应用程序非常快速的感觉,所有内容都只需点击一下即可访问。

  • 当使用自定义标签页时,应用程序不会被移除,因为即使标签页位于其顶部,应用程序级别仍然在前台。因此,在整个使用期间我们保持在顶级(除非电话来电或其他用户交互导致变更)。

  • 使用相同的 Chrome 容器意味着用户已经登录到他们过去连接的网站;之前授予的特定权限在这里同样适用;甚至填充数据、自动完成和同步在这里也能工作。

  • Chrome 自定义标签页允许我们在预 Lollipop 设备上为用户提供最新的浏览器实现,这些设备上的 WebView 不是最新版本。

实现指南

如先前所讨论的,Chrome 自定义标签页集成了几个功能。第一个是自定义用户界面与自定义标签页的交互。第二个可以让页面加载更快,并保持应用程序活跃。

我们可以使用 Chrome 自定义标签页吗?

在我们开始使用自定义标签之前,我们希望确保它们得到支持。Chrome 自定义标签公开了一个服务,因此最好的支持检查就是尝试绑定到该服务。成功意味着自定义标签得到支持并且可以使用。你可以查看这个代码片段,它展示了一个帮助器如何进行检查,或者稍后查看项目源代码:

gist.github.com/MaTriXy/5775cb0ff98216b2a99d

在确认并了解到存在支持之后,我们将从 UI 和交互部分开始。

自定义 UI 和标签交互

在这里,我们将使用众所周知的ACTION_VIEW意图动作,并通过向发送到 Chrome 的意图中附加额外内容,触发 UI 的变化。记住,ACTION_VIEW意图与所有浏览器兼容,包括 Chrome。有些手机没有 Chrome,或者设备的默认浏览器不是 Chrome 的情况也时有发生。在这些情况下,用户将导航到特定的浏览器应用。

意图是我们传递给 Chrome 额外数据的一种便捷方式。

在调用 Chrome 自定义标签时,不要使用这些标志:

  • FLAG_ACTIVITY_NEW_TASK

  • FLAG_ACTIVITY_NEW_DOCUMENT

在使用 API 之前,我们需要将其添加到我们的gradle文件中:

compile 'com.android.support:customtabs:23.1.0'

这将允许我们在应用中使用自定义标签支持库:

CustomTabsIntent.EXTRA_SESSION

之前的代码是自定义标签支持库的额外内容;它用于匹配会话。在打开自定义标签时,必须在意图中包含它。如果不需要与意图匹配任何服务端会话,它可以为 null。

注意

我们有一个示例项目,展示了 UI 的选项,名为ChubbyTabby,在github.com/MaTriXy/ChubbyTabby

我们这里也将讨论重要的部分。我们的主要交互来自于支持库中一个特殊的构建器,称为CustomTabsIntent.Builder;这个类将帮助我们构建自定义标签所需的意图。

CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(); //init our Builder

//Setting Toolbar Color
int color = getResources().getColor(R.color.primary);

//we use primary color for our toolbar as well - you can define any color you want and use it.
intentBuilder.setToolbarColor(color);

//Enabling Title showing
intentBuilder.setShowTitle(true);

//this will show the title in the custom tab along the url showing at the bottom part of the tab toolbar.

//This part is adding custom actions to the over flow menu
String menuItemTitle = getString(R.string.menu_title_share);
PendingIntent menuItemPendingIntent = createPendingShareIntent();
intentBuilder.addMenuItem(menuItemTitle, menuItemPendingIntent);
String menuItemEmailTitle = getString(R.string.menu_title_email);
PendingIntent menuItemPendingIntentTwo = createPendingEmailIntent();
intentBuilder.addMenuItem(menuItemEmailTitle, menuItemPendingIntentTwo);

//Setting custom Close Icon.
intentBuilder.setCloseButtonIcon(mCloseButtonBitmap);

//Adding custom icon with custom action for the share action.
intentBuilder.setActionButton(mActionButtonBitmap, getString(R.string.menu_title_share), createPendingShareIntent());

//Setting start and exit animation for the custom tab.
intentBuilder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left);
intentBuilder.setExitAnimations(this, android.R.anim.slide_in_left, android.R.anim.slide_out_right);
CustomTabActivityHelper.openCustomTab(this, intentBuilder.build(), Uri.parse(URL), new WebviewFallback(), useCustom);

这里需要注意的几件事如下:

  • 每个菜单项都使用了一个待定意图;如果你不知道待定意图是什么,可以访问:

    developer.android.com/reference/android/app/PendingIntent.html

  • 当我们设置自定义图标,比如关闭按钮或动作按钮时,我们使用位图,并且在传递给构建器之前必须对位图进行解码

  • 设置动画很容易,你可以使用之前创建的动画 XML 文件;只需确保在发布应用之前测试结果。

下面的截图是一个 Chrome 自定义标签的例子:

自定义 UI 和标签交互

自定义动作按钮

作为开发者,我们对自定义标签中显示的操作按钮拥有完全控制权。在大多数用例中,我们可以考虑一个分享动作,或者可能是用户会执行的一个更常见的选项。操作按钮基本上是一个捆绑包,包含动作按钮的图标和待定意图,当用户点击动作按钮时,Chrome 会调用它。根据规范,图标的高度应为 24 dp,宽度应为 24-48 dp。

//Adding custom icon with custom action for the share action
intentBuilder.setActionButton(mActionButtonBitmap, getString(R.string.menu_title_share), createPendingShareIntent());

配置自定义菜单

默认情况下,Chrome 自定义标签通常在顶部始终显示带有前进页面信息刷新三个图标的行,而在页面中查找在浏览器中打开(在 Chrome 中打开)也可能出现在菜单底部。

作为开发者,我们有能力添加和定制最多三个菜单项,这些菜单项将显示在图标行和底部项目之间,如下面的截图所示:

配置自定义菜单

我们看到的菜单实际上由一系列包含菜单文本和待定意图的捆绑包表示,当用户点击该项目时,Chrome 会代表我们调用这些意图。

//This part is adding custom buttons to the over flow menu
String menuItemTitle = getString(R.string.menu_title_share);
PendingIntent menuItemPendingIntent = createPendingShareIntent();
intentBuilder.addMenuItem(menuItemTitle, menuItemPendingIntent);
String menuItemEmailTitle = getString(R.string.menu_title_email);
PendingIntent menuItemPendingIntentTwo = createPendingEmailIntent();
intentBuilder.addMenuItem(menuItemEmailTitle, menuItemPendingIntentTwo);

配置自定义进入和退出动画

没有动画伴随总是不完整的。这里也不例外,因为我们需要制作两个过渡:一个是自定义标签进入,另一个是退出;我们可以为每个开始和退出动画设置特定的动画:

//Setting start and exit animation for the custom tab.
intentBuilder.setStartAnimations(this,R.anim.slide_in_right, R.anim.slide_out_left);
intentBuilder.setExitAnimations(this, android.R.anim.slide_in_left, android.R.anim.slide_out_right);

Chrome 预热

通常,在使用意图构建器设置好意图之后,我们应该调用CustomTabsIntent.launchUrl(Activity context, Uri url)方法,这是一个非静态方法,会触发新的自定义标签活动来加载 URL,并在自定义标签中显示。这可能会花费一些时间,并影响应用提供的流畅性体验。

我们都知道用户要求近乎即时的体验,所以 Chrome 有一个我们可以连接的服务,并要求它预热浏览器及其本地组件。调用这个方法会要求 Chrome 执行以下操作:

  • 对 URL 的主域进行 DNS 预解析

  • 对最可能使用的子资源的 DNS 预解析

  • 与目的地的预连接,包括 HTTPS/TLS 协商

预热 Chrome 的过程如下:

  1. 连接到服务。

  2. 附加一个导航回调,以便在页面加载完成时得到通知。

  3. 在服务中,调用warmup方法在后台启动 Chrome。

  4. 创建newSession;此会话用于所有 API 请求。

  5. 使用mayLaunchUrl告诉 Chrome 用户可能会加载哪些页面。

  6. 使用步骤 4 中生成的会话 ID 启动意图。

连接到 Chrome 服务

连接到 Chrome 服务需要处理Android 接口定义语言AIDL)。

如果你不了解 AIDL,请阅读:

developer.android.com/guide/components/aidl.html

接口是用 AIDL 创建的,它会自动为我们创建一个代理服务类:

CustomTabsClient.bindCustomTabsService()

因此,我们会检查 Chrome 包名称;在我们的示例项目中,我们有一个特殊的方法来检查在各种变体中是否包含 Chrome。设置包之后,我们绑定到服务,并获取一个 CustomTabsClient 对象,我们可以使用它直到与服务断开连接:

pkgName - This is one of several options checking to see if we have a version of Chrome installed can be one of the following
static final String STABLE_PACKAGE = "com.android.chrome";
static final String BETA_PACKAGE = "com.chrome.beta";
static final String DEV_PACKAGE = "com.chrome.dev";
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";

private CustomTabsClient mClient;

// Binds to the service.
CustomTabsClient.bindCustomTabsService(myContext, pkgName, new CustomTabsServiceConnection() {
  @Override
  public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
    // CustomTabsClient should now be valid to use
    mClient = client;
  }

  @Override
  public void onServiceDisconnected(ComponentName name) {
  // CustomTabsClient is no longer valid which also invalidates sessions.
    mClient = null;
  }
});

绑定到服务后,我们可以调用我们需要的适当方法。

加热浏览器进程

该方法如下所示:

boolean CustomTabsClient.warmup(long flags)

//With our valid client earlier we call the warmup method.
mClient.warmup(0);

注意

当前未使用标志,因此现在我们传递 0

温习过程会加载本地库和浏览器进程,以便稍后支持自定义标签浏览。这个过程是异步的,返回值表示请求是否已被接受。它返回 true 以表示成功。

创建新标签会话

该方法如下所示:

boolean CustomTabsClient.newSession(ICustomTabsCallback callback)

新标签会话用作将 mayLaunchUrl 调用、我们构建的 VIEW 意图以及生成的标签捆绑在一起的分组对象。我们可以获取与创建的会话相关联的回调,该回调将传递给任何连续的 mayLaunchUrl 调用。此方法在成功创建会话时返回 CustomTabsSession;否则,它返回 Null

设置预取 URL

该方法如下所示:

boolean CustomTabsSession.mayLaunchUrl (Uri url, Bundle extras, List<Bundle> otherLikelyBundles)

此方法将通知浏览器,很快就会导航到此 URL。在调用此方法之前,请确保先执行 warmup() ——这是必须的。首先必须指定最可能的 URL,我们可以发送一个可选的其他可能 URL 列表(otherLikelyBundles)。列表必须按降序排序,可选列表可能会被忽略。对此方法的新的调用将降低之前调用的优先级,可能导致 URL 没有被预取。布尔值告诉我们操作是否已成功完成。

自定义标签连接回调

该方法如下所示:

void CustomTabsCallback.onNavigationEvent (int navigationEvent, Bundle extras)

我们在自定义标签的每次导航事件中都有触发回调。int navigationEvent 元素定义页面状态的六个中的一个。更多信息请参考以下代码:

//Sent when the tab has started loading a page.
public static final int NAVIGATION_STARTED = 1;
//Sent when the tab has finished loading a page.
public static final int NAVIGATION_FINISHED = 2;
//Sent when the tab couldn't finish loading due to a failure.
public static final int NAVIGATION_FAILED = 3;
//Sent when loading was aborted by a user action.
public static final int NAVIGATION_ABORTED = 4;
//Sent when the tab becomes visible.
public static final int TAB_SHOWN = 5;
//Sent when the tab becomes hidden.
public static final int TAB_HIDDEN = 6;
private static class NavigationCallback extends CustomTabsCallback {
  @Override
  public void onNavigationEvent(int navigationEvent, Bundle extras) {
    Log.i(TAG, "onNavigationEvent: Code = " + navigationEvent);
  }
}

总结

我们了解到一项新增加的功能——Chrome 自定义标签,它允许我们将网页内容嵌入到我们的应用程序中并修改用户界面。Chrome 自定义标签使我们可以为用户提供更加完整、快速的应用内网页体验。我们在幕后使用 Chrome 引擎,这比常规 WebView 加载更快,甚至比加载整个 Chrome(或其他浏览器)应用程序还要快。

我们看到我们可以在后台预加载页面,使得我们的数据看起来非常快。我们可以自定义 Chrome 标签的外观,使其与我们的应用相匹配。我们看到的改变包括工具栏颜色、过渡动画,甚至是在工具栏中添加自定义操作。

自定义标签页同样能够享受到 Chrome 浏览器的功能,比如保存的密码、自动填充、点击搜索以及同步;所有这些功能在自定义标签页内都可以使用。对于开发者来说,集成这些功能相当简单,在基础层面上仅需添加几行代码。如果需要,支持库可以帮助实现更复杂的集成。

这是 Chrome 浏览器的一个功能,意味着在任何安装了最新版 Chrome 的 Android 设备上都可以使用它。请记住,随着新功能和修复的推出,Chrome 自定义标签页支持库会发生变化,这与其他支持库是一样的,因此请更新您的版本,并确保使用最新的 API 以避免任何问题。

在下一章节中,我们将深入探讨一下 Android Marshmallow 提供的一些新的认证/安全特性。

第八章:认证

安卓棉花糖引入了一个全新集成的 API,以更好地支持用户认证和用户验证。我们现在可以在带有指纹扫描器的设备上使用新的Fingerprint API 来认证用户。我们还可以设置特定时间,以便在应用登录时认为用户锁屏验证有效。在本章中,我们将尝试介绍这些新增功能并解释如何使用它们:

  • 指纹认证 API

  • 凭据宽限期

  • 明文网络流量

指纹认证 API

安卓棉花糖现在允许我们,开发者,在使用支持设备上的此类认证扫描器时,通过用户的指纹扫描来认证用户。

Fingerprint API 是通过全新的包添加到安卓棉花糖中的:

android.hardware.fingerprint

包含四个类:

  • FingerprintManager

  • FingerprintManager.AuthenticationCallback

  • FingerprintManager.AuthenticationResult

  • FingerprintManager.CryptoObject

每个类在我们的指纹认证过程中都有特定的角色。

我们如何使用指纹识别进行认证?

android.hardware.fingerprint包中的前四个类可以用以下方式解释:

  • FingerprintManager:管理对指纹硬件的访问

  • FingerprintManager.AuthenticationCallback:在auth过程中使用的回调

  • FingerprintManager.AuthenticationResultauth过程的 结果容器

  • FingerprintManager.CryptoObject:与FingerprintManager一起使用的特定Crypto对象

假设我们希望通过指纹来认证用户。必须使用带有指纹传感器的设备;否则,我们无法使用此 API。我们需要获取FingerprintManager的实例,然后调用authenticate()方法。我们必须为指纹认证流程实现特定的用户界面,并且源代码中包含了标准的 Android 指纹图标(c_fp_40px.png)。我们需要在应用清单中添加适当的权限:

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

目前,我们没有带有指纹传感器的设备,因此我们需要从模拟器中测试我们的代码。(Nexus 5X 和 Nexus 6P 仍然供应有限)

设置测试环境

必须安装至少是 Android SDK Tools 修订版 24.3。现在,我们导航到设置 | 安全 | 指纹并添加一个指纹。

设置测试环境

手动遵循指导说明;我们被要求选择 PIN 码,并引导我们找到以下截图:

设置测试环境

最后,我们必须使用一个特殊的adb命令,欺骗传感器捕获一个模拟指纹:

adb -e emu finger touch <finger_id>

结果屏幕应如下面的截图所示:

设置测试环境

我们使用finger_id =1来模拟单个指纹。同样的命令也可以在锁屏或我们的应用中模拟指纹触摸事件。

设置测试环境

如果需要帮助设置模拟器,请阅读:

安卓设备索引

现在,我们可以启动我们的应用程序,并看到当用户购买商品时,我们可以使用指纹作为认证方式。

凭据宽限期

你是否有过在设备解锁后想立即使用应用程序,却发现需要重新登录或再次输入应用密码的经历?现在,我们可以查询设备并检查它最近是否解锁以及解锁的时间有多近。这将给我们的用户一个避免使用我们应用程序带来麻烦的机会。请注意,这必须与用户身份验证的公钥或私钥实现结合使用。如果你想了解更多关于Android Keystore System的信息,请访问developer.android.com/training/articles/keystore.html

我们使用KeyguardManager并通过isKeyguardSecure()方法检查锁屏是否安全。一旦确认安全,我们可以尝试使用该功能;否则,这意味着用户没有设置安全的锁屏,这个功能就是一项a-no-op

我们在 Android KeyStore 中使用KeyGenerator生成一个对称密钥,只有在用户在过去x秒内使用设备凭据进行认证后才能使用。设置这个值(x)是在设置KeyGeneratorKeyPairGenerator时通过setUserAuthenticationValidityDurationSeconds()方法完成的。

凭据宽限期

你可以查看示例代码了解更多信息。该活动被称为CredGraceActivity

注意事项

尽可能少地尝试显示重新认证对话框。在使用加密对象时,应尝试验证其有效期,只有在通过的情况下,才使用createConfirmDeviceCredentialIntent()重新认证用户。

明文网络流量

安卓棉花糖(Marshmallow)在清单文件中增加了一个新的标志。该标志指示应用程序是否正在使用如 HTTP 这样的明文网络流量。该标志是android:usesCleartextTraffic,默认值为true。将其设置为false意味着一些系统 API 组件——如 HTTP 和 FTP 堆栈、DownloadManagerMediaPlayer——将拒绝发出 HTTP 流量,只允许 HTTPS。构建一个尊重此设置的第三方库将是一个好习惯。为什么这样做是好的?因为明文流量缺乏机密性、真实性和防篡改保护,数据可能会在未被发现的情况下被篡改。这对于应用程序来说是一个主要风险,现在我们可以用它来尝试强制执行更强大、更安全的数据传输到/来自我们的应用程序。

我们需要记住,这个标志是基于最佳努力的,不可能阻止所有来自安卓应用程序的明文流量,因为它们有权限使用Socket API,例如,Socket API 无法确定明文的使用。我们可以通过从ApplicationInfo.flagsNetworkSecurityPolicy.isCleartextTrafficPermitted()读取来检查这个标志。

注意

WebView不尊重这个标志,这意味着即使标志为false,它也会加载 HTTP。

那么,我们要如何处理明文网络流量标志呢?

在应用程序开发期间,我们可以使用StrictMode并通过StrictMode.VmPolicy.Builder.detectCleartextNetwork()来识别应用程序中的任何明文流量。

usesCleartextTraffic的缺点是,当它不使用SSL(即安全套接层的简称)时,会导致应用程序崩溃或进程终止。这在理论上是很好的,但在生产环境中,由于某些原因,你的 SSL 证书出现问题,你将流量重定向到 HTTP,这就不好了。因此,要特别注意你的应用中哪里使用了 HTTPS,哪里可以使用 HTTP。

幸运的是,我们有StrictMode,现在它有一种方法可以通过StrictMode.VmPolicy.Builder上的detectCleartextNetwork()方法来警告您的应用程序是否执行了任何未加密的网络操作。在我们的示例项目中,我们有一个ClearTextNetworkUsageActivity活动;当运行TestStrictHttp productFlavor变体时,你会在LogCat中看到这个。

那么,我们要如何处理明文网络流量标志呢?

总结

安卓棉花糖为我们提供了一个新的 API,通过Fingerprint API 对用户进行身份验证。我们可以使用传感器,甚至在我们的应用程序中对用户进行身份验证,如果我们想节省用户登录的需要,还可以将其保存以后使用,利用安卓棉花糖引入的凭据宽限期功能。

我们还介绍了一种方法,通过仅使用 HTTPS 和StrictMode策略,使我们的应用程序更加安全,该策略通过usesCleartextTraffic标志实施,这使我们能够确保所有连接到外部世界的节点都需要安全的连接。

感谢您的阅读。

我想感谢安卓团队。这个产品改变了我的生活。

安卓生态系统拥有伟大的开发者们,他们通过发布库、撰写博文和回答支持问题做出贡献;我为能成为其中一员感到骄傲。

期待未来的版本。

posted @ 2024-05-23 11:07  绝不原创的飞龙  阅读(17)  评论(0编辑  收藏  举报