Android 4.2 API

API 级别:17

Android 4.2 (JELLY_BEAN_MR1) 是对 Jelly Bean 版本的更新,为用户和应用开发者提供了新功能。本文将向开发者介绍最值得关注、最实用的新 API。

作为应用开发者,您应尽快从 SDK 管理器下载 Android 4.2 系统映像和 SDK 平台。如果您没有运行 Android 4.2 的设备来测试应用,请使用 Android 4.2 系统映像在 Android 模拟器上测试您的应用。然后,针对 Android 4.2 平台构建您的应用,以开始使用最新的 API。

为了更好地针对搭载 Android 4.2 的设备优化您的应用,您应将 targetSdkVersion 设置为 "17",将其安装在 Android 4.2 系统映像上并进行测试,然后发布包含此变更的更新。

您可以使用 Android 4.2 中的 API,同时支持较低版本,方法是在代码中添加条件,在执行您的 minSdkVersion 不支持的 API 之前检查系统 API 级别。如需详细了解如何保持向后兼容性,请参阅创建向后兼容界面

如需详细了解 API 级别的工作原理,请参阅什么是 API 级别?

重要的行为变更

如果您之前发布过 Android 应用,请注意以下可能会影响应用行为的更改:

  • 默认情况下,系统不再导出 Content Provider。也就是说,android:exported 属性的默认值现在为 “false"。如果其他应用必须能够访问您的 content provider,您现在必须明确设置 android:exported="true"

    仅当您将 android:targetSdkVersionandroid:minSdkVersion 设为 17 或更高版本时,此更改才会生效。否则,即使在 Android 4.2 及更高版本上运行时,默认值仍为 “true"

  • 与以前的 Android 版本相比,如果您的应用请求了 ACCESS_COARSE_LOCATION 权限但未请求 ACCESS_FINE_LOCATION 权限,用户位置信息结果可能不太准确。

    当您的应用请求获取粗略位置信息(而非精确位置信息)的权限时,为了满足用户在隐私保护方面的期望,系统不会提供比城市街区更准确的用户位置信息估算值。

  • Settings.System 定义的某些设备设置现在为只读状态。如果应用尝试写入对 Settings.System 中定义的设置所做的更改(已移至 Settings.Global),则在 Android 4.2 及更高版本上运行时,写入操作将静默失败。

    即使您的 android:targetSdkVersionandroid:minSdkVersion 值低于 17,您的应用在 Android 4.2 及更高版本上运行时也无法修改已移至 Settings.Global 的设置。

  • 如果您的应用使用 WebView,Android 4.2 会额外增加一层安全保障,因此您可以更安全地将 JavaScript 绑定到 Android 代码。如果您将 targetSdkVersion 设为 17 或更高版本,现在则必须将 @JavascriptInterface 注解添加到您希望可供 JavaScript 使用的任何方法(该方法也必须为公开方法)。如果您未提供注解,那么在 Android 4.2 或更高版本上运行时,WebView 中的网页将无法访问该方法。如果您将 targetSdkVersion 设置为 16 或更低版本,则无需添加该注释,但我们建议您更新目标版本并添加该注释,以提高安全性。

    详细了解如何将 JavaScript 代码绑定到 Android 代码

Daydream

Daydream 是适用于 Android 设备的全新互动式屏保模式。当设备插入基座时,或设备在插入充电器时处于空闲状态(而非关闭屏幕)时,它会自动激活。Daydream 一次显示一个梦,可能是纯视觉的被动显示屏,在触摸时关闭,也可能是交互式的,并且能够响应整套输入事件。梦想在应用进程中运行,并且拥有对 Android 界面工具包(包括视图、布局和动画)的完整访问权限,因此它们比动态壁纸或应用微件更加灵活和强大。

您可以通过实现 DreamService 的子类为 Daydream 创建梦想。DreamService API 在设计上与 Activity API 类似。如需为 Dream 指定界面,请在创建窗口后的任意时间点(例如从 onAttachedToWindow() 回调)将布局资源 ID 或 View 传递给 setContentView()

DreamService 类在基础 Service API 之上提供其他重要的生命周期回调方法,例如 onDreamingStarted()onDreamingStopped()onDetachedFromWindow()。您无法从应用启动 DreamService,它由系统自动启动。

如果您的 Dream 是交互式的,您可以从梦想中启动一个 activity,将用户引导至应用的完整界面,以便获取更多详细信息或进行更多控制。您可以使用 finish() 结束梦想,以便用户可以看到新的 activity。

如需将 Daydream 提供给系统,请在清单文件中使用 <service> 元素声明 DreamService。然后,您必须添加一个具有操作 "android.service.dreams.DreamService" 的 intent 过滤器。例如:

<service android:name=".MyDream" android:exported="true"
    android:icon="@drawable/dream_icon" android:label="@string/dream_label" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

请注意,DreamService 中还有一些其他的实用方法:

  • setInteractive(boolean) 用于控制 Dream 是接收输入事件,还是在用户输入后立即退出。如果梦境是互动式的,则用户可以使用返回主屏幕按钮退出梦境,或者您可以调用 finish() 来停止梦境。
  • 如果您想获得完全沉浸式的显示屏,可以调用 setFullscreen() 来隐藏状态栏。
  • 在 Daydream 启动之前,显示屏会变暗,以向用户表明空闲超时即将到来。调用 setScreenBright(true) 可让您将显示屏设置为正常亮度。

如需了解详情,请参阅 DreamService 文档。

辅助显示屏

Android 现在允许您的应用在通过有线连接或 Wi-Fi 连接到用户设备的其他屏幕上显示独特的内容。如需为辅助显示屏创建独特的内容,请扩展 Presentation 类并实现 onCreate() 回调。在 onCreate() 中,通过调用 setContentView() 为辅助屏幕指定界面。作为 Dialog 类的扩展,Presentation 类会提供可让应用在辅助显示屏上显示唯一界面的区域。

如需检测可以显示 Presentation 的辅助显示屏,请使用 DisplayManagerMediaRouter API。虽然 DisplayManager API 可让您枚举可同时连接的多个显示屏,但您通常应该使用 MediaRouter 来快速访问系统默认的演示文稿显示屏。

如需获取演示文稿的默认显示屏,请调用 MediaRouter.getSelectedRoute() 并向其传递 ROUTE_TYPE_LIVE_VIDEO。这将返回一个 MediaRouter.RouteInfo 对象,用于描述系统当前为视频呈现选择的路由。如果 MediaRouter.RouteInfo 不为 null,请调用 getPresentationDisplay() 以获取表示已连接屏幕的 Display

然后,您可以将 Display 对象传递给 Presentation 类的构造函数来展示演示文稿。现在,您的演示文稿将显示在辅助显示屏上。

如需在运行时检测新屏幕是否已连接,请创建一个 MediaRouter.SimpleCallback 实例,并在其中实现 onRoutePresentationDisplayChanged() 回调方法,系统会在连接新的演示文稿屏幕时调用该方法。然后,注册 MediaRouter.SimpleCallback,方法是将其与 ROUTE_TYPE_LIVE_VIDEO 路线类型一起传递给 MediaRouter.addCallback()。当您收到对 onRoutePresentationDisplayChanged() 的调用时,只需按上述方法调用 MediaRouter.getSelectedRoute() 即可。

如需针对辅助屏幕进一步优化 Presentation 中的界面,您可以通过在已应用于应用或 activity 的 <style> 中指定 android:presentationTheme 属性来应用不同的主题。

请注意,连接到用户设备的屏幕通常具有较大的屏幕尺寸,并且可能具有不同的屏幕密度。由于屏幕特性可能不同,因此您应提供专门针对此类大屏幕设备优化的资源。如果您需要从 Presentation 请求其他资源,请调用 getContext().getResources() 来获取与屏幕对应的 Resources 对象。这可以从您的应用中提供最适合辅助显示屏的屏幕尺寸和密度的相应资源。

如需了解详情并查看一些代码示例,请参阅 Presentation 类文档。

锁屏微件

Android 现在允许用户向锁定屏幕添加应用 widget。如需使您的应用微件可在锁定屏幕上使用,请将 android:widgetCategory 属性添加到用于指定 AppWidgetProviderInfo 的 XML 文件中。此属性支持两个值:home_screenkeyguard。默认情况下,该属性设置为 home_screen,以便用户可以将您的应用 widget 添加到主屏幕。如果您希望应用 widget 在锁定屏幕上也可使用,请添加 keyguard 值:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:widgetCategory="keyguard|home_screen">
</appwidget-provider>

您还应该使用 android:initialKeyguardLayout 属性为处于锁定屏幕上的应用 widget 指定初始布局。这与 android:initialLayout 的工作方式相同,因为它提供了一个布局,该布局可以立即显示,直到应用 widget 完成初始化并且能够更新布局。

如需详细了解如何针对锁定屏幕构建应用 widget,包括在锁定屏幕上适当调整应用 widget 的大小,请参阅应用 widget 指南。

多用户

Android 现在允许在平板电脑等可共用设备上设置多个用户空间。设备上的每位用户都有自己的一组帐号、应用、系统设置、文件以及任何其他与用户关联的数据。

作为应用开发者,要使您的应用在一台设备上与多位用户一起正常发挥作用,您需要采取的行动没有什么不同。无论设备上可能有多少用户,您的应用为给定用户保存的数据会与应用为其他用户保存的数据分开。系统会跟踪哪些用户数据属于运行您的应用的用户进程,并仅向您的应用提供相应用户的数据访问权限,而不允许应用访问其他用户的数据。

在多用户环境中保存数据

每当您的应用保存用户偏好设置、创建数据库或将文件写入用户的内部或外部存储空间时,相应数据只能在以该用户的身份运行时访问。

为确保您的应用在多用户环境中正常运行,请勿使用硬编码路径引用您的内部应用目录或外部存储位置,而应始终使用适当的 API:

无论您使用其中哪个 API 为指定用户保存数据,在以其他用户身份运行时将无法访问数据。从应用的角度来看,每个用户都是在完全独立的设备上运行。

在多用户环境中识别用户

如果您的应用想要识别唯一身份用户(例如收集分析数据或创建其他帐号关联),应遵循识别唯一安装的推荐做法。通过在您的应用首次启动时创建新的 UUID,您一定会获得用于跟踪每个用户的唯一 ID,无论有多少用户在一台设备上安装了您的应用。或者,您也可以保存从服务器提取的本地令牌,或使用 Google Cloud Messaging 提供的注册 ID。

请注意,如果您的应用请求其中一个硬件设备标识符(例如 Wi-Fi MAC 地址或 SERIAL 编号),它们将为每个用户提供相同的值,因为这些标识符与硬件而非用户绑定。更不用说这些标识符带来的其他问题,如识别应用安装这篇博文所述。

新的全局设置

添加了 Settings.Global,更新了系统设置,以支持多用户。此集合与 Settings.Secure 设置类似,因为它们是只读的,但会应用于设备上的所有用户空间。

多项现有设置从 Settings.SystemSettings.Secure 转移到了此处。如果您的应用目前正在对之前在 Settings.System 中定义的设置(例如 AIRPLANE_MODE_ON)进行更改,那么您应该预料到,如果这些设置已移至 Settings.Global,那么此操作将无法再在搭载 Android 4.2 或更高版本的设备上生效。您可以继续读取 Settings.Global 中的设置,但由于系统不再认为这些设置可供应用更改,因此尝试更改时将静默失败,并且在 Android 4.2 或更高版本上运行应用时,系统会向系统日志写入一条警告。

RTL 布局支持

Android 现在提供了一些 API,可让您构建界面,从而合理转换布局方向,以支持使用从右到左 (RTL) 界面和阅读方向的语言(例如阿拉伯语和希伯来语)。

如需开始在您的应用中支持 RTL 布局,请在清单文件中将 android:supportsRtl 属性设置为 <application> 元素,并将其设置为 “true"。启用此选项后,系统将启用各种 RTL API,以便以 RTL 布局显示您的应用。例如,操作栏的右侧会显示图标和标题,左侧会显示操作按钮,而您使用框架提供的 View 类创建的任何布局也将反转。

如果您需要进一步优化应用以 RTL 布局显示时的外观,可以采用以下两个基本的优化级别:

  1. 将向左和向右的布局属性转换为面向开始和结束的布局属性。

    例如,使用 android:layout_marginStart 代替 android:layout_marginLeft,使用 android:layout_marginEnd 代替 android:layout_marginRight

    RelativeLayout 类还提供了用于替换左侧/右侧位置的相应布局属性,例如,用 android:layout_alignParentStart 替换 android:layout_alignParentLeftandroid:layout_toStartOf 替换 android:layout_toLeftOf

  2. 或者,为了对 RTL 布局提供全面的优化,您可以使用 ldrtl 资源限定符(ldrtl 表示布局方向从右到左)提供完全独立的布局文件。例如,您可以将默认布局文件保存在 res/layout/ 中,并将针对 RTL 优化的布局保存在 res/layout-ldrtl/ 中。

    ldrtl 限定符非常适合可绘制资源,因此您可以提供朝向与阅读方向对应的方向的图形。

整个框架中也提供了各种其他 API 来支持 RTL 布局,例如在 View 类中可用于为自定义视图实现适当的行为,而在 Configuration 中用于查询当前的布局方向。

注意:如果您使用的是 SQlite,并且拥有“只有数字”的表或列名称,那么要注意:如果您的设备设置为阿拉伯语语言区域,那么使用 String.format(String, Object...) 可能会导致数字被转换为对应的阿拉伯语语言区域错误。您必须使用 String.format(Locale,String,Object...) 确保将数字保留为 ASCII 格式。此外,还要使用 String.format("%d", int)(而非 String.valueOf(int))来设置数字格式。

嵌套 Fragment

您现在可以在 fragment 内嵌入 fragment。如果您想要将可重复使用的动态界面组件放入一个本身是动态且可重复使用的界面组件中,则此功能非常有用。例如,如果您使用 ViewPager 创建左右滑动并占用大部分屏幕空间的 fragment,现在可以将 fragment 插入到每个 fragment 页面中。

如需嵌套 fragment,只需对要添加 fragment 的 Fragment 调用 getChildFragmentManager() 即可。这将返回一个 FragmentManager,您可以像平常从顶级 activity 中那样使用它来创建 fragment 事务。例如,下面是从现有 Fragment 类中添加 fragment 的一些代码:

Kotlin

val videoFragment = VideoPlayerFragment()
childFragmentManager.beginTransaction().apply {
    add(R.id.video_fragment, videoFragment)
    commit()
}

Java

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

从嵌套 fragment 中,您可以通过调用 getParentFragment() 获取对父 fragment 的引用。

Android 支持库现在支持嵌套 fragment,因此您可以在 Android 1.6 及更高版本中实现嵌套 fragment 设计。

注意:如果布局包含 <fragment>,则无法将布局膨胀到 fragment 中。嵌套 fragment 只有在以动态方式添加到 fragment 中才受支持。

Renderscript

Renderscript 计算功能通过以下功能得到增强:

脚本内建函数

您可以使用 Renderscript 的内置脚本内建函数为您实现常见操作,例如:

如需使用脚本内建函数,请调用每个内建函数的静态 create() 方法来创建脚本实例。然后,您可以调用每个脚本内建函数的可用 set() 方法,以设置任何必要的输入和选项。 最后,调用 forEach() 方法来执行此脚本。

脚本组

ScriptGroup 可让您将相关的 Renderscript 脚本链接在一起,并通过一次调用来执行这些脚本。

使用 ScriptGroup.Builder,通过调用 addKernel() 将所有脚本添加到该组中。添加所有脚本后,通过调用 addConnection() 在脚本之间创建连接。添加完连接后,调用 create() 以创建脚本组。在执行脚本组之前,请指定输入 Allocation 和要使用 setInput(Script.KernelID, Allocation) 方法运行的初始脚本,并提供输出 Allocation 以写入结果,并使用 setOutput() 运行最终脚本。最后,调用 execute() 以运行脚本组。

过滤条件

Filterscript 定义了现有 Renderscript API 的限制,允许生成的代码在更广泛的处理器(CPU、GPU 和 DSP)上运行。如需创建 Filterscript 文件,请创建 .fs 文件(而不是 .rs 文件),并指定 #pragma rs_fp_relaxed 以告知 Renderscript 运行时,您的脚本不需要严格的 IEEE 754-2008 浮点精度。此精度允许非规格化为清零,并允许向零舍入。此外,您的 Filterscript 脚本不得使用 32 位内置类型,并且必须使用 __attribute__((kernel)) 属性指定自定义的根函数,因为 Filterscript 不支持指针,而指针是由 root() 函数的默认签名定义的。

注意:虽然平台支持 Filterscript,但 SDK 工具版本 21.0.1 中将提供开发者支持。

如需详细了解 Android 4.2 中的所有 API 变更,请参阅 API 差异报告