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 系统映像上并进行测试,然后发布包含此变更的更新。
您可以在使用 Android 4.2 中的 API 的同时,还支持旧版本,方法是在代码中添加条件,先检查系统 API 级别,然后再执行 minSdkVersion
不支持的 API。如需详细了解如何保持向后兼容性,请参阅创建向后兼容界面。
如需详细了解 API 级别的工作原理,请参阅什么是 API 级别?
重要的行为变更
如果您之前发布过 Android 应用,请注意以下可能会影响应用行为的更改:
- 默认情况下,Content Provider 不再导出。也就是说,
android:exported
属性的默认值现在为“false"
。如果其他应用必须能够访问您的 content provider,那么现在您必须明确设置android:exported="true"
。只有当您将
android:targetSdkVersion
或android:minSdkVersion
设置为 17 或更高版本时,此更改才会生效。否则,即使应用在 Android 4.2 及更高版本上,默认值也仍为“true"
。 - 与以前的 Android 版本相比,如果您的应用请求
ACCESS_COARSE_LOCATION
权限,但未请求ACCESS_FINE_LOCATION
权限,用户位置信息结果的准确性可能会降低。当您的应用请求粗略位置信息(而非精确位置信息)权限时,为了满足用户的隐私期望,系统不会提供比城市街区更准确的用户位置信息估算值。
- 由
Settings.System
定义的某些设备设置现在处于只读状态。如果您的应用尝试写入对Settings.System
中定义的设置所做的更改,并且这些更改已移至Settings.Global
,则在 Android 4.2 及更高版本上运行时,写入操作将静默失败。即使您的应用在 Android 4.2 及更高版本上运行时,即使
android:targetSdkVersion
和android:minSdkVersion
的值小于 17,也无法修改已移至Settings.Global
的设置。 - 如果您的应用使用
WebView
,Android 4.2 会额外添加一层安全防护,以便您更安全地将 JavaScript 绑定到您的 Android 代码。如果您将targetSdkVersion
设置为 17 或更高版本,则现在必须将@JavascriptInterface
注解添加到您希望可供 JavaScript 使用的任何方法(该方法也必须公开)。如果您未提供注解,那么在 Android 4.2 或更高版本上运行时,WebView
中的网页将无法访问该方法。如果您将targetSdkVersion
设置为 16 或更低版本,则无需使用该注释,但我们建议您更新目标版本并添加该注释,以提高安全性。
Daydream
Daydream 是适用于 Android 设备的全新互动式屏保模式。当设备插入基座时,或当设备插入充电器时处于空闲状态(而非关闭屏幕)时,该检测就会自动激活。Daydream 每次显示一个梦,该梦可能是纯视觉的被动显示屏,会在触摸时关闭,也可能是交互式的,并且能够对全套输入事件做出响应。您的梦想在应用的进程中运行,并且拥有对 Android 界面工具包(包括视图、布局和动画)的完全访问权限,因此它们比动态壁纸或应用 widget 更灵活、更强大。
您可以通过实现 DreamService
的子类为 Daydream 打造梦想。DreamService
API 的设计与 Activity
API 的设计类似。如需为 Dream 指定界面,请在创建窗口后随时将布局资源 ID 或 View
传递给 setContentView()
,例如从 onAttachedToWindow()
回调传递。
DreamService
类在 Service
基本 API 之上提供其他重要的生命周期回调方法,例如 onDreamingStarted()
、onDreamingStopped()
和 onDetachedFromWindow()
。您无法从应用启动 DreamService
,它由系统自动启动。
如果您的 Dream 是交互式的,您可以从 Dream 中启动一个 activity,将用户引导至应用的完整界面,以便获取更多详细信息或进行控制。您可以使用 finish()
结束 Dream,以便用户可以看到新的 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 是接收输入事件,还是在用户输入后立即退出。如果 Dream 是交互式的,则用户可以使用 Back 或 Home 按钮退出 Dream,或者您可以调用finish()
来停止 Dream。- 如果您希望实现完全沉浸式的显示屏,可以调用
setFullscreen()
来隐藏状态栏。 - 在 Daydream 启动之前,显示屏会变暗,以向用户表明闲置超时即将到来。调用
setScreenBright(true)
可让您改为将显示屏设置为常规亮度。
如需了解详情,请参阅 DreamService
文档。
辅助显示屏
Android 现在允许您的应用在通过有线连接或 Wi-Fi 连接到用户设备的其他屏幕上显示独特的内容。如需为辅助屏幕创建独特的内容,请扩展 Presentation
类并实现 onCreate()
回调。在 onCreate()
中,通过调用 setContentView()
为辅助屏幕指定界面。作为 Dialog
类的扩展,Presentation
类提供应用可以在辅助屏幕上显示唯一界面的区域。
如需检测可以显示 Presentation
的辅助屏幕,请使用 DisplayManager
或 MediaRouter
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_screen
和 keyguard
。默认情况下,此属性会设置为 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:
- 如需访问内部存储空间,请使用
getFilesDir()
、getCacheDir()
或openFileOutput()
。 - 如需访问外部存储空间,请使用
getExternalFilesDir()
或getExternalStoragePublicDirectory()
。
无论您使用其中哪个 API 为指定用户保存数据,以其他用户身份运行时将无法访问数据。从应用的角度来看,每个用户都是在完全独立的设备上运行。
在多用户环境中识别用户
如果您的应用需要识别唯一身份用户,例如收集分析数据或创建其他帐号关联,则应遵循识别唯一身份安装的推荐做法。通过在您的应用首次启动时创建新的 UUID
,您肯定会获得一个唯一 ID 来跟踪每位用户,无论有多少用户在一台设备上安装了您的应用。或者,您也可以保存从服务器提取的本地令牌,或使用 Google Cloud Messaging 提供的注册 ID。
请注意,如果您的应用请求其中一个硬件设备标识符(例如 Wi-Fi MAC 地址或 SERIAL
号码),它们将为每个用户提供相同的值,因为这些标识符是与硬件绑定,而不是用户。更不用说像识别应用安装这篇博文中所讨论的这些标识符引入的其他问题。
新的全局设置
添加了 Settings.Global
,更新了系统设置,以支持多个用户。此集合与 Settings.Secure
设置类似,因为它们是只读的,但会全局应用于设备上的所有用户空间。
多项现有设置从 Settings.System
或 Settings.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 布局显示时的外观,有两个基本的优化措施:
- 将向左和向右的布局属性转换为面向开始和结束的布局属性。
例如,使用
android:layout_marginStart
代替android:layout_marginLeft
,使用android:layout_marginEnd
代替android:layout_marginRight
。RelativeLayout
类还提供了用于替换左侧/右侧位置的相应布局属性,例如使用android:layout_alignParentStart
替换android:layout_alignParentLeft
和android:layout_toStartOf
,而不是使用android:layout_toLeftOf
。 - 或者,为了对 RTL 布局提供全面的优化,您可以使用
ldrtl
资源限定符(ldrtl
表示布局方向从右到左)提供完全独立的布局文件。例如,您可以将默认布局文件保存在res/layout/
中,并将 RTL 优化布局保存在res/layout-ldrtl/
中。ldrtl
限定符非常适合可绘制资源,因此您可以提供朝向与阅读方向对应的方向的图形。
整个框架中也提供了各种其他 API 来支持 RTL 布局,例如在 View
类中提供了这些 API,以便您为自定义视图实现适当的行为以及在 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 时,才支持嵌套 fragment。
Renderscript
Renderscript 计算功能通过以下功能得到增强:
- 脚本内建函数
您可以使用 Renderscript 的内置脚本内建函数为您实现常见操作,例如:
Blends
Blur
Color matrix
3x3 convolve
5x5 convolve
Per-channel lookup table
Converting an Android YUV buffer to RGB
如需使用脚本内建函数,请调用每个内建函数的静态
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 差异报告。