Android 4.2 API

API 级别:17

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

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

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

通过向代码添加条件,您可以先检查系统 API 级别,然后再执行您的 minSdkVersion 不支持的 API,这样您就可以使用 Android 4.2 中的 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 界面工具包(包括视图、布局和动画)的完全访问权限,因此它们比动态壁纸或应用 widget 更加灵活和强大。

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

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

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

如需向系统提供您的互动屏保,请在清单文件中使用 <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_alignParentLeftandroid:layout_toLeftOfandroid:layout_alignParentStartandroid:layout_toStartOf

  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 脚本链接在一起,并通过一次调用执行这些脚本。

调用 addKernel(),使用 ScriptGroup.Builder 将所有脚本添加到该组中。添加所有脚本后,通过调用 addConnection() 在脚本之间创建连接。添加完连接后,调用 create() 以创建脚本组。在执行脚本组之前,请使用 setInput(Script.KernelID, Allocation) 方法指定要运行的输入 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 差异报告