API 级别:18
Android 4.3 (JELLY_BEAN_MR2
) 是对 Jelly Bean 版本的更新,为用户和应用开发者提供了新功能。本文档介绍了其中最值得关注的新 API。
作为应用开发者,您应尽快从 SDK 管理器下载 Android 4.3 系统映像和 SDK 平台。如果您没有运行 Android 4.3 的设备来测试应用,请使用 Android 4.3 系统映像在 Android 模拟器上测试您的应用。然后,针对 Android 4.3 平台构建您的应用,以开始使用最新的 API。
更新目标 API 级别
为了更好地针对搭载 Android 4.3 的设备优化您的应用,您应将 targetSdkVersion
设置为 "18"
,将其安装在 Android 4.3 系统映像上并进行测试,然后发布包含此变更的更新。
您可以使用 Android 4.3 中的 API,同时支持较低版本,方法是在代码中添加条件,在执行您的 minSdkVersion
不支持的 API 之前检查系统 API 级别。如需详细了解如何保持向后兼容性,请参阅支持不同平台版本。
Android 支持库还提供了多种 API,供您在旧版平台上实现新功能。
如需详细了解 API 级别的工作原理,请参阅什么是 API 级别?
重要的行为变更
如果您之前发布过 Android 应用,请注意您的应用可能会受到 Android 4.3 变更的影响。
如果您的应用使用隐式 intent...
您的应用在受限配置文件环境中可能会出现异常。
受限个人资料环境中的用户可能无法使用所有标准 Android 应用。例如,受限个人资料可能会停用网络浏览器和相机应用。因此,您的应用不应假设哪些应用可用,因为如果您在未验证应用是否可用于处理 Intent
的情况下调用 startActivity()
,您的应用可能会在受限资料中崩溃。
使用隐式 intent 时,您应始终通过调用 resolveActivity()
或 queryIntentActivities()
来验证应用是否可用于处理该 intent。例如:
Kotlin
val intent = Intent(Intent.ACTION_SEND) ... if (intent.resolveActivity(packageManager) != null) { startActivity(intent) } else { Toast.makeText(context, R.string.app_not_available, Toast.LENGTH_LONG).show() }
Java
Intent intent = new Intent(Intent.ACTION_SEND); ... if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(context, R.string.app_not_available, Toast.LENGTH_LONG).show(); }
如果您的应用依赖于帐号...
您的应用在受限配置文件环境中可能会出现异常。
默认情况下,受限个人资料环境中的用户无权访问用户帐号。如果您的应用依赖于 Account
,那么在受限资料中使用时,应用可能会崩溃或出现意外行为。
如果您的应用依赖于敏感的帐号信息,因此如果您想完全阻止受限个人资料使用您的应用,请在清单的 <application>
元素中指定 android:requiredAccountType
属性。
如果您想允许受限个人资料在他们无法自行创建帐号的情况下继续使用您的应用,则可以停用需要帐号的应用功能,或允许受限个人资料访问主要用户创建的帐号。如需了解详情,请参阅下面有关支持受限付款资料中的帐号的部分。
如果您的应用使用 VideoView...
您的视频在 Android 4.3 设备上可能会缩小。
在以前的 Android 版本中,VideoView
widget 错误地将 layout_height
和 layout_width
的 "wrap_content"
值计算为与 "match_parent"
相同。因此,虽然针对高度或宽度使用 "wrap_content"
之前可能已提供了所需的视频布局,但这样做可能会导致 Android 4.3 及更高版本的视频更小。若要解决此问题,请将 "wrap_content"
替换为 "match_parent"
,并验证您的视频在 Android 4.3 以及较低版本中是否按预期显示。
受限个人资料
在 Android 平板电脑上,用户现在可以根据主要用户创建受限个人资料。用户创建受限个人资料时,可以启用一些限制,例如该个人资料可以使用哪些应用。借助 Android 4.3 中的一组新 API,您还可以为自己开发的应用构建精细的限制设置。例如,通过使用这些新 API,您可以允许用户控制您的应用在受限配置文件环境中运行时可用的内容类型。
供用户控制您构建的限制的界面由系统的“设置”应用管理。如需向用户显示应用的限制设置,您必须创建接收 ACTION_GET_RESTRICTION_ENTRIES
intent 的 BroadcastReceiver
,以声明应用提供的限制。系统会调用此 intent 以查询所有应用的可用限制,然后构建界面以允许主要用户管理每个受限个人资料的限制。
在 BroadcastReceiver
的 onReceive()
方法中,您必须为应用提供的每个限制创建一个 RestrictionEntry
。每个 RestrictionEntry
都会定义限制标题、说明和以下数据类型之一:
TYPE_BOOLEAN
表示限制结果为 true 或 false。TYPE_CHOICE
,适用于具有多个互斥选择的限制(单选按钮选项)。TYPE_MULTI_SELECT
,适用于具有多个不互斥的选项(复选框选项)的限制。
然后,将所有 RestrictionEntry
对象放入 ArrayList
中,并将其作为 EXTRA_RESTRICTIONS_LIST
extra 的值放入广播接收器的结果中。
系统会在“设置”应用中为应用限制创建界面,并使用您为每个 RestrictionEntry
对象提供的唯一键来保存每项限制。当用户打开您的应用时,您可以通过调用 getApplicationRestrictions()
查询任何当前限制。这将返回一个 Bundle
,其中包含您使用 RestrictionEntry
对象定义的每个限制的键值对。
如果您希望提供无法由布尔值、单选题和多选值处理的更具体的限制,则可以创建一个 activity,用户可以在其中指定限制,并允许用户通过限制设置打开该 activity。在您的广播接收器中,将 EXTRA_RESTRICTIONS_INTENT
extra 包含在结果 Bundle
中。此 extra 必须指定 Intent
,以指示要启动的 Activity
类(使用 putParcelable()
方法通过 intent 传递 EXTRA_RESTRICTIONS_INTENT
)。当主要用户进入您的 activity 以设置自定义限制时,您的 activity 随后必须使用 EXTRA_RESTRICTIONS_LIST
或 EXTRA_RESTRICTIONS_BUNDLE
键返回包含额外限制值的结果,具体取决于您是分别指定 RestrictionEntry
对象还是键值对。
支持受限个人资料中的帐号
添加到主要用户的任何帐号都可以访问受限个人资料,但默认情况下,无法通过 AccountManager
API 访问这些帐号。如果您尝试在受限个人资料中使用 AccountManager
添加帐号,则会收到失败结果。由于存在这些限制,您有以下三种选择:
如需通过受限个人资料访问帐号,您必须将 android:restrictedAccountType
属性添加到 <application> 标记:
<application ... android:restrictedAccountType="com.example.account.type" >
注意:启用此属性后,您的应用可通过受限个人资料访问主要用户的帐号。因此,只有当应用显示的信息不会泄露被视为敏感信息的个人身份信息 (PII) 时,您才应允许此操作。系统设置将告知主要用户您的应用向其帐号授予受限个人资料,因此用户应明确说明帐号访问权限对于应用的功能至关重要。如果可能,您还应为主要用户提供足够的限制控制措施,定义在您的应用中允许多少帐号访问权限。
如果您想要使用帐号,但实际上并不需要帐号来实现应用的主要功能,则可以检查帐号是否可用,并在不可用时停用相应功能。您应该首先检查是否有可用的帐号。如果没有,请通过调用 getUserRestrictions()
查询是否可以创建新帐号,并检查结果中的 DISALLOW_MODIFY_ACCOUNTS
extra。如果值为 true
,则应停用需要访问帐号的应用功能。例如:
Kotlin
val um = context.getSystemService(Context.USER_SERVICE) as UserManager val restrictions: Bundle = um.userRestrictions if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { // cannot add accounts, disable some functionality }
Java
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); Bundle restrictions = um.getUserRestrictions(); if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { // cannot add accounts, disable some functionality }
注意:在这种情况下,您不应在清单文件中声明任何新属性。
如果因为应用依赖于帐号中的敏感个人信息,并且受限个人资料目前无法添加新帐号,导致受限个人资料无法访问,请将 android:requiredAccountType
属性添加到 <application> 标记中:
<application ... android:requiredAccountType="com.example.account.type" >
例如,Gmail 应用使用此属性为受限个人资料停用自身功能,因为受限个人资料不应该访问所有者的个人电子邮件地址。
无线和连接
低功耗蓝牙(Smart Ready)
Android 现在支持通过 android.bluetooth
中的新 API 支持蓝牙低功耗 (LE)。
借助这些新 API,您可以构建与蓝牙低功耗外围设备(如心率监测器和计步器)通信的 Android 应用。
由于蓝牙 LE 是一项硬件功能,并非所有 Android 设备都提供该功能,因此您必须在清单文件中声明 "android.hardware.bluetooth_le"
的 <uses-feature>
元素:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
如果您已熟悉 Android 的传统蓝牙 API,请注意使用蓝牙 LE API 会有一些差异。最重要的是,现在应使用 BluetoothManager
类执行一些高级操作,例如获取 BluetoothAdapter
、获取已连接设备的列表以及检查设备状态。例如,您现在应通过以下方式获取 BluetoothAdapter
:
Kotlin
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter
Java
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter();
如需发现蓝牙 LE 外围设备,请对 BluetoothAdapter
调用 startLeScan()
,并向其传递 BluetoothAdapter.LeScanCallback
接口的实现。当蓝牙适配器检测到蓝牙 LE 外围设备时,您的 BluetoothAdapter.LeScanCallback
实现会收到对 onLeScan()
方法的调用。此方法为您提供了一个表示检测到的设备的 BluetoothDevice
对象、设备的 RSSI 值,以及一个包含设备通告记录的字节数组。
如果您只想扫描特定类型的外围设备,则可以调用 startLeScan()
,然后添加 UUID
对象数组,以指定您的应用支持的 GATT 服务。
注意:您只能扫描蓝牙 LE 设备,或使用旧版 API 扫描传统蓝牙设备。您无法同时扫描 LE 和传统蓝牙设备。
之后如需连接到蓝牙 LE 外围设备,请对相应的 BluetoothDevice
对象调用 connectGatt()
,并向其传递 BluetoothGattCallback
的实现。您的 BluetoothGattCallback
实现会收到关于设备连接状态和其他事件的回调。在 onConnectionStateChange()
回调期间,如果该方法将 STATE_CONNECTED
作为新状态传递,您就可以开始与设备通信。
如要在设备上访问蓝牙功能,您的应用还需要请求特定的蓝牙用户权限。如需更多信息,请参阅蓝牙低功耗 API 指南。
WLAN 仅扫描模式
尝试识别用户所在位置时,Android 可能会使用 Wi-Fi 通过扫描附近的接入点来帮助确定位置。不过,为了节省电量,用户经常会保持 Wi-Fi 关闭,从而导致位置数据不太准确。Android 现在包含一种仅扫描模式,在该模式下,设备 Wi-Fi 可以扫描接入点,从而在不连接到接入点的情况下获取位置信息,从而大幅降低耗电量。
如果您想获取用户的位置信息,但 Wi-Fi 当前处于关闭状态,您可以调用 startActivity()
并使用 ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE
操作,请求用户启用 Wi-Fi 仅扫描模式。
Wi-Fi 配置
借助新的 WifiEnterpriseConfig
API,面向企业的服务可以自动执行受管设备的 Wi-Fi 配置。
快速回复来电
从 Android 4.0 开始,我们推出了一项称作“快速回复”的功能,可让用户使用即时短信回复来电,而无需接听来电或解锁设备。到目前为止,这些快速消息始终由默认的即时通讯应用处理。现在,任何应用都可以通过为 ACTION_RESPOND_VIA_MESSAGE
创建一个带有 intent 过滤器的 Service
来声明自己能够处理这些消息。
当用户通过快速响应来响应来电时,“电话”应用会发送 ACTION_RESPOND_VIA_MESSAGE
intent,其中 URI 用于描述接听者(来电者),EXTRA_TEXT
extra 包含用户想要发送的消息。当您的服务收到 intent 时,它应该在传递消息后立即自行停止运行(应用不应显示 activity)。
为了接收此 intent,您必须声明 SEND_RESPOND_VIA_MESSAGE
权限。
多媒体
MediaExtractor 和 MediaCodec 增强功能
现在,Android 让您可以更轻松地按照 ISO/IEC 23009-1 标准,使用 MediaCodec
和 MediaExtractor
中的现有 API 编写自己的基于 HTTP 的动态自适应流式传输 (DASH) 播放器。这些 API 底层的框架已更新为支持解析碎片化 MP4 文件,但您的应用仍负责解析 MPD 元数据并将各个流传递给 MediaExtractor
。
如果要将 DASH 用于加密内容,请注意 getSampleCryptoInfo()
方法会返回描述每个加密媒体样本结构的 MediaCodec.CryptoInfo
元数据。此外,getPsshInfo()
方法已添加到 MediaExtractor
,以便您可以访问 DASH 媒体的 PSSH 元数据。此方法会返回 UUID
对象到字节的映射,其中 UUID
指定加密方案,字节是该方案特有的数据。
媒体数字版权管理
新的 MediaDrm
类通过将 DRM 问题与媒体播放分离开来,为媒体内容提供数字版权管理 (DRM) 的模块化解决方案。例如,通过这种 API 分离,您无需使用 Widevine 媒体格式即可播放 Widevine 加密内容。此 DRM 解决方案还支持 DASH 通用加密,因此您可以为流式传输内容使用各种 DRM 方案。
您可以使用 MediaDrm
获取不透明的密钥请求消息,并处理来自服务器的密钥响应消息,以便获取和配置许可。您的应用负责处理与服务器的网络通信;MediaDrm
类仅提供生成和处理消息的功能。
MediaDrm
API 旨在与 Android 4.1(API 级别 16)中引入的 MediaCodec
API 结合使用,其中包括用于对内容进行编码和解码的 MediaCodec
、用于处理加密内容的 MediaCrypto
,以及用于提取和解复用内容的 MediaExtractor
。
您必须先构建 MediaExtractor
和 MediaCodec
对象。然后,您便可以访问用于标识 DRM 方案的 UUID
(通常来自内容中的元数据),并使用它通过其构造函数构造 MediaDrm
对象的实例。
从 Surface 进行视频编码
Android 4.1(API 级别 16)添加了 MediaCodec
类,用于对媒体内容进行低级编码和解码。对视频进行编码时,Android 4.1 要求您向媒体提供 ByteBuffer
数组,但 Android 4.3 现在允许您使用 Surface
作为编码器的输入。例如,这可让您对来自现有视频文件或使用通过 OpenGL ES 生成的帧的输入进行编码。
如需使用 Surface
作为编码器的输入,请先为 MediaCodec
调用 configure()
。然后,调用 createInputSurface()
以接收可用于流式传输媒体内容的 Surface
。
例如,您可以将给定的 Surface
传递给 eglCreateWindowSurface()
,从而将其用作 OpenGL 上下文的窗口。然后,在渲染 Surface 时,调用 eglSwapBuffers()
以将帧传递给 MediaCodec
。
如需开始编码,请对 MediaCodec
调用 start()
。完成后,调用 signalEndOfInputStream()
以终止编码,然后对 Surface
调用 release()
。
媒体多路复用
新的 MediaMuxer
类支持在一个音频流与一个视频流之间进行多路复用。这些 API 相当于 Android 4.2 中添加的 MediaExtractor
类,用于对媒体进行多路复用(多路复用)操作。
支持的输出格式在 MediaMuxer.OutputFormat
中定义。MP4 是唯一受支持的输出格式,MediaMuxer
目前一次仅支持一个音频流和/或一个视频流。
MediaMuxer
主要用于与 MediaCodec
配合使用,因此您可以通过 MediaCodec
执行视频处理,然后通过 MediaMuxer
将输出保存到 MP4 文件中。您还可以将 MediaMuxer
与 MediaExtractor
结合使用,在无需编码或解码的情况下执行媒体修改。
RemoteControlClient 的播放进度和拖动操作
在 Android 4.0(API 级别 14)中,添加了 RemoteControlClient
以启用来自遥控器客户端的媒体播放控件,例如锁定屏幕上的控件。Android 4.3 现在为此类控制器提供了显示播放位置以及用于拖动播放操作的控件的功能。如果您已使用 RemoteControlClient
API 为媒体应用启用了遥控器,则可以通过实现两个新接口来允许拖动播放。
首先,您必须通过将 FLAG_KEY_MEDIA_POSITION_UPDATE
标志传递给 setTransportControlsFlags()
来启用该标志。
然后,实现以下两个新接口:
RemoteControlClient.OnGetPlaybackPositionListener
- 其中包括回调
onGetPlaybackPosition()
,当遥控器需要更新其界面中的进度时,该回调会请求媒体的当前位置。 RemoteControlClient.OnPlaybackPositionUpdateListener
- 其中包括回调
onPlaybackPositionUpdate()
,该回调会在用户使用遥控器界面拖动播放播放时告知应用媒体的新时间代码。将播放更新为新位置后,请调用
setPlaybackState()
以指示新的播放状态、位置和速度。
定义这些接口后,您可以通过分别调用 setOnGetPlaybackPositionListener()
和 setPlaybackPositionUpdateListener()
为 RemoteControlClient
设置它们。
图形
对 OpenGL ES 3.0 的支持
Android 4.3 添加了 Java 接口和对 OpenGL ES 3.0 的原生支持。OpenGL ES 3.0 中提供的主要新功能包括:
- 加速实现高级视觉效果
- 将高质量 ETC2/EAC 纹理压缩作为标准特征
- 支持整数和 32 位浮点的新版 GLSL ES 着色语言
- 高级纹理渲染
- 纹理大小和渲染缓冲区格式的更广泛的标准化
Android 上 OpenGL ES 3.0 的 Java 接口随 GLES30
提供。使用 OpenGL ES 3.0 时,请务必在清单文件中使用 <uses-feature> 标记和 android:glEsVersion
属性对其进行声明。例如:
<manifest> <uses-feature android:glEsVersion="0x00030000" /> ... </manifest>
此外,请记得通过调用 setEGLContextClientVersion()
并传递 3
作为版本来指定 OpenGL ES 上下文。
如需详细了解如何使用 OpenGL ES,包括如何在运行时检查设备支持的 OpenGL ES 版本,请参阅 OpenGL ES API 指南。
可绘制对象的 mipmap
使用 mipmap 作为位图或可绘制对象的来源是一种提供高质量图像和各种图像缩放的简单方法,如果您希望在动画播放期间缩放图片,这种方法特别有用。
Android 4.2(API 级别 17)在 Bitmap
类中添加了对 mipmap 的支持 - 如果您提供了 mipmap 来源并启用 setHasMipMap()
,Android 会交换 Bitmap
中的 mip 图片。现在,在 Android 4.3 中,您还可以通过提供 mipmap 资源并在位图资源文件中设置 android:mipMap
属性,或通过调用 hasMipMap()
来为 BitmapDrawable
对象启用 mipmap。
界面
查看叠加层
新的 ViewOverlay
类在 View
之上提供了一个透明层,您可以在其中添加视觉内容,而这不会影响布局层次结构。您可以通过调用 getOverlay()
获取任何 View
的 ViewOverlay
。叠加层的大小和位置始终与其宿主视图(创建时所使用的视图)相同,这让您可以添加显示在宿主视图前面的内容,但无法扩展该宿主视图的边界。
如果您要创建动画(例如将视图滑动到其容器之外或在屏幕上移动项目而不影响视图层次结构),那么使用 ViewOverlay
特别有用。不过,由于叠加层的可用区域受限于与其宿主视图相同的区域,因此,如果您想为某个视图在布局中超出其位置的位置添加动画效果,则必须使用父视图中具有所需布局边界的叠加层。
为 widget 视图(如 Button
)创建叠加层时,您可以通过调用 add(Drawable)
将 Drawable
对象添加到叠加层。如果您为布局视图调用 getOverlay()
(例如 RelativeLayout
),返回的对象将是 ViewGroupOverlay
。ViewGroupOverlay
类是 ViewOverlay
的子类,您还可以通过调用 add(View)
添加 View
对象。
注意:您向叠加层添加的所有可绘制对象和视图都只是可视化内容。它们无法接收焦点或输入事件。
例如,以下代码为向右滑动的视图添加动画效果,具体方法是将该视图置于父视图的叠加层中,然后对该视图执行转换动画:
Kotlin
val view: View? = findViewById(R.id.view_to_remove) val container: ViewGroup? = view?.parent as ViewGroup container?.apply { overlay.add(view) ObjectAnimator.ofFloat(view, "translationX", right.toFloat()) .start() }
Java
View view = findViewById(R.id.view_to_remove); ViewGroup container = (ViewGroup) view.getParent(); container.getOverlay().add(view); ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", container.getRight()); anim.start();
光学边界布局
对于包含九宫图背景图片的视图,您现在可以指定它们应根据背景图片的“光学”边界(而不是视图的“裁剪”边界)与相邻视图对齐。
例如,图 1 和图 2 分别显示了相同的布局,但图 1 中的版本使用的是裁剪边界(默认行为),而图 2 使用的是光学边界。由于用于按钮和相框模式的 9-patch 图像的边缘周围有内边距,因此在使用裁剪边界时,它们似乎彼此不对齐,也不会与文本对齐。
注意:图 1 和图 2 中的屏幕截图启用了“显示布局边界”开发者设置。对于每个视图,红线表示光学边界,蓝线表示裁剪边界,粉色表示外边距。
![](https://developer.android.google.cn/static/images/tools/clipbounds_2x.png?authuser=5&hl=bg)
图 1. 使用裁剪边界的布局(默认)。
![](https://developer.android.google.cn/static/images/tools/opticalbounds_2x.png?authuser=5&hl=bg)
图 2. 使用光学边界的布局。
如需根据视图的光学边界对齐视图,请在其中一个父布局中将 android:layoutMode
属性设置为 "opticalBounds"
。例如:
<LinearLayout android:layoutMode="opticalBounds" ... >
![](https://developer.android.google.cn/static/images/tools/ninepatch_opticalbounds_2x.png?authuser=5&hl=bg)
图 3. Holo 按钮 9-patch 的放大视图,具有光学边界。
为此,应用于视图背景的九宫格图像必须使用九宫格文件底部和右侧的红线指定光学边界(如图 3 所示)。红线表示应从裁剪边界中减去的区域,留下图像的光学边界。
为布局中的 ViewGroup
启用光学边界后,所有后代视图都会继承光学边界布局模式,除非您通过将 android:layoutMode
设置为 "clipBounds"
来替换该组的光学边界。所有布局元素还遵循其子视图的光学边界,根据其内视图的光学边界调整自己的边界。不过,布局元素(ViewGroup
的子类)目前不支持应用于其自身背景的九宫格图像的光学边界。
如果您通过为 View
、ViewGroup
或其任何子类创建子类来创建自定义视图,您的视图将继承这些光学绑定行为。
注意:Holo 主题支持的所有 widget 都已使用光学边界进行了更新,包括 Button
、Spinner
、EditText
等。因此,如果您的应用采用 Holo 主题(Theme.Holo
、Theme.Holo.Light
等),您可以将 android:layoutMode
属性设置为 "opticalBounds"
,从而立即受益。
如需使用 Draw 9-patch 工具为您自己的 9-patch 图像指定光学边界,请在点击边框像素时按住 Control 键。
Rect 值的动画
现在,您可以使用新的 RectEvaluator
在两个 Rect
值之间添加动画效果。这个新类是 TypeEvaluator
的实现,可传递给 ValueAnimator.setEvaluator()
。
窗口附加和焦点监听器
以前,如果您想监听视图何时附加/分离到窗口或何时其焦点发生变化,则需要替换 View
类以分别实现 onAttachedToWindow()
和 onDetachedFromWindow()
或 onWindowFocusChanged()
。
现在,如需接收附加和分离事件,您可以改为实现 ViewTreeObserver.OnWindowAttachListener
,并使用 addOnWindowAttachListener()
在视图上设置它。如需接收焦点事件,您可以实现 ViewTreeObserver.OnWindowFocusChangeListener
,并使用 addOnWindowFocusChangeListener()
在视图上设置它。
电视过扫描支持
为确保您的应用填满每台电视的整个屏幕,您现在可以为应用布局启用过扫描。过扫描模式由 FLAG_LAYOUT_IN_OVERSCAN
标志决定,您可以使用平台主题(如 Theme_DeviceDefault_NoActionBar_Overscan
)或通过在自定义主题中启用 windowOverscan
样式来启用该标记。
屏幕方向
<activity>
标记的 screenOrientation
属性现在支持其他值,以遵循用户对自动屏幕旋转的偏好:
"userLandscape"
- 行为与
"sensorLandscape"
相同,除非用户停用自动屏幕旋转,否则它会锁定为正常横向方向,并且不会翻转。 "userPortrait"
- 行为与
"sensorPortrait"
相同,以下情况除外:如果用户停用自动屏幕旋转,则它会锁定在正常的纵向方向,并且不会翻转。 "fullUser"
- 行为与
"fullSensor"
相同,并且允许所有四个方向旋转,但如果用户停用自动屏幕旋转,则它会锁定在用户的首选方向。
此外,您现在还可以声明 "locked"
,将应用屏幕方向锁定为当前屏幕方向。
旋转动画
通过 WindowManager
中新的 rotationAnimation
字段,您可以在系统切换屏幕方向时从要使用的三种动画中选择其一。三个动画分别是:
注意:这些动画仅在您已将 activity 设置为使用“全屏”模式时才可用,您可以使用 Theme.Holo.NoActionBar.Fullscreen
等主题来启用该模式。
例如,要启用“淡入淡出”动画,请执行以下操作:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val params: WindowManager.LayoutParams = window.attributes params.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE window.attributes = params ... }
Java
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowManager.LayoutParams params = getWindow().getAttributes(); params.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; getWindow().setAttributes(params); ... }
用户输入
新传感器类型
借助新的 TYPE_GAME_ROTATION_VECTOR
传感器,您可以检测设备的旋转,而无需担心磁场干扰。与 TYPE_ROTATION_VECTOR
传感器不同,TYPE_GAME_ROTATION_VECTOR
并非基于磁北。
新的 TYPE_GYROSCOPE_UNCALIBRATED
和 TYPE_MAGNETIC_FIELD_UNCALIBRATED
传感器提供原始传感器数据,而不考虑偏差估算。也就是说,现有的 TYPE_GYROSCOPE
和 TYPE_MAGNETIC_FIELD
传感器提供的传感器数据分别将设备中陀螺仪漂移和硬铁的估算偏差考虑在内。这些传感器的新“未校准”版本则提供原始传感器数据并单独提供估算偏差值。借助这些传感器,您可以使用外部数据增强估算偏差,从而提供自己的传感器数据自定义校准方法。
通知监听器
Android 4.3 添加了新的服务类 NotificationListenerService
,让您的应用可以在系统发布新通知时接收有关它们的信息。
如果您的应用目前使用无障碍服务 API 访问系统通知,您应更新应用以改用这些 API。
联系人提供程序
查询“可联系对象”
新的联系人提供程序查询 Contactables.CONTENT_URI
提供了一种有效的方法来获取一个 Cursor
,其中包含属于指定查询匹配的所有联系人的全部电子邮件地址和电话号码。
查询联系人增量
联系人提供程序中新增了 API,让您可以高效地查询联系人数据的最新更改。以前,当联系人数据中的某些内容发生更改时,您的应用会收到通知,但您并不知道具体发生了哪些更改,并且需要检索所有联系人,然后遍历这些联系人以发现更改。
如需跟踪插入和更新方面的更改,您现在可以在选择中包含 CONTACT_LAST_UPDATED_TIMESTAMP
参数,以仅查询自您上次查询提供程序以来更改的联系人。
为了跟踪已删除的联系人,新表格 ContactsContract.DeletedContacts
提供了已删除的联系人日志(但每个删除的联系人会在此表中保留一段有限的时间)。与 CONTACT_LAST_UPDATED_TIMESTAMP
类似,您可以使用新的选择参数 CONTACT_DELETED_TIMESTAMP
检查自您上次查询提供程序以来哪些联系人已被删除。该表还包含常量 DAYS_KEPT_MILLISECONDS
,该常量包含日志保留的天数(以毫秒为单位)。
此外,当用户通过系统设置菜单清除联系人存储空间时,联系人提供程序现在会广播 CONTACTS_DATABASE_CREATED
操作,从而有效地重新创建联系人提供程序数据库。它旨在向应用表明,它们需要删除存储的所有联系信息,并使用新查询重新加载。
如需获取使用这些 API 检查联系人更改的示例代码,请参阅 SDK 示例下载中提供的 ApiDemos 示例。
本地化
改进了对双向文本的支持
以前的 Android 版本支持从右到左 (RTL) 的语言和布局,但有时无法正确处理混合方向文本。因此,Android 4.3 添加了 BidiFormatter
API,可帮助您正确设置包含相反方向内容的文本格式,而不会对其进行乱码。
例如,如果您想创建一个包含字符串变量的语句,如“您是不是要找 15 Bay Street, Laurel, CA?”,您通常会将本地化的字符串资源和变量传递给 String.format()
:
Kotlin
val suggestion = String.format(resources.getString(R.string.did_you_mean), address)
Java
Resources res = getResources(); String suggestion = String.format(res.getString(R.string.did_you_mean), address);
不过,如果语言区域是希伯来语,那么格式化的字符串将如下所示:
האם התכוונת ל 15 Bay Street, Laurel, CA?
因为“15”应该是“Bay Street”的左边的,所以输入的地址是不对的。解决方案是使用 BidiFormatter
及其 unicodeWrap()
方法。例如,上面的代码变为:
Kotlin
val bidiFormatter = BidiFormatter.getInstance() val suggestion = String.format( resources.getString(R.string.did_you_mean), bidiFormatter.unicodeWrap(address) )
Java
Resources res = getResources(); BidiFormatter bidiFormatter = BidiFormatter.getInstance(); String suggestion = String.format(res.getString(R.string.did_you_mean), bidiFormatter.unicodeWrap(address));
默认情况下,unicodeWrap()
使用第一强方向估算启发式算法,如果文本方向的第一个信号不能代表整体内容的正确方向,则可能会出错。如有必要,您可以通过将 TextDirectionHeuristics
中的一个 TextDirectionHeuristic
常量传递给 unicodeWrap()
来指定不同的启发法。
注意:这些新 API 也可以通过 Android 支持库(包含 BidiFormatter
类和相关 API)用于以前的 Android 版本。
无障碍服务
处理按键事件
AccessibilityService
现在可以使用 onKeyEvent()
回调方法接收按键输入事件的回调。这样,无障碍服务就可以处理基于按键的输入设备(例如键盘)的输入,并将这些事件转换为以前只能通过触控输入或设备的方向键才能实现的特殊操作。
选择文本并复制/粘贴
AccessibilityNodeInfo
现在提供一些 API,这些 API 可让 AccessibilityService
选择、剪切、复制和粘贴文本,然后将其粘贴到节点中。
如需指定要剪切或复制的文本的选择区,无障碍服务可以使用新操作 ACTION_SET_SELECTION
,并通过 ACTION_ARGUMENT_SELECTION_START_INT
和 ACTION_ARGUMENT_SELECTION_END_INT
向其传递选择的开始和结束位置。或者,您也可以使用现有操作 ACTION_NEXT_AT_MOVEMENT_GRANULARITY
(以前仅用于移动光标位置)操控光标位置,然后添加参数 ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
,从而选择文本。
然后,您可以使用 ACTION_CUT
和 ACTION_COPY
进行剪切或复制,然后使用 ACTION_PASTE
进行粘贴。
注意:这些新的 API 也可以通过包含 AccessibilityNodeInfoCompat
类的 Android 支持库用于以前的 Android 版本。
声明无障碍功能
从 Android 4.3 开始,无障碍服务必须在其元数据文件中声明无障碍功能,才能使用某些无障碍功能。如果元数据文件中未请求该功能,则该功能将为空操作。如需声明服务的无障碍功能,您必须使用与 AccessibilityServiceInfo
类中的各种“功能”常量对应的 XML 属性。
例如,如果服务不请求 flagRequestFilterKeyEvents
功能,则不会接收按键事件。
测试和调试
自动化界面测试
新的 UiAutomation
类提供了一些 API,可让您模拟用户操作以实现测试自动化。借助平台的 AccessibilityService
API,您可以通过 UiAutomation
API 检查屏幕内容并注入任意键盘和触摸事件。
如需获取 UiAutomation
的实例,请调用 Instrumentation.getUiAutomation()
。为实现此功能,在从 adb shell
运行 InstrumentationTestCase
时,必须在 instrument
命令中提供 -w
选项。
借助 UiAutomation
实例,您可以执行任意事件以测试应用,具体方法是调用 executeAndWaitForEvent()
,并向其传递要执行的 Runnable
、操作的超时期限以及 UiAutomation.AccessibilityEventFilter
接口的实现。您将在 UiAutomation.AccessibilityEventFilter
实现中收到一个调用,该调用可让您过滤您感兴趣的事件,并确定给定测试用例成功与否。
如需观察测试期间的所有事件,请创建 UiAutomation.OnAccessibilityEventListener
的实现,并将其传递给 setOnAccessibilityEventListener()
。然后,每次事件发生时,您的监听器接口都会收到对 onAccessibilityEvent()
的调用,并收到描述该事件的 AccessibilityEvent
对象。
UiAutomation
API 以非常低的级别公开了各种其他操作,以鼓励开发界面测试工具(如 uiautomator)。例如,UiAutomation
还可以:
- 注入输入事件
- 更改屏幕方向
- 截取屏幕截图
最重要的是,对于界面测试工具,UiAutomation
API 可以跨应用边界工作,这一点与 Instrumentation
中的不同。
适用于应用的 Systrace 事件
Android 4.3 添加了 Trace
类,其中包含 beginSection()
和 endSection()
两个静态方法,以便您定义要包含在 systrace 报告中的代码块。通过在应用中创建可跟踪代码部分,systrace 日志能够让您更详细地分析应用中发生速度缓慢问题的位置。
如需了解如何使用 Systrace 工具,请参阅使用 Systrace 分析展示广告和性能。
安全性
适用于应用私钥的 Android 密钥库
Android 现在在 KeyStore
工具中提供了一个名为“Android 密钥库”的自定义 Java 安全提供程序,此提供程序可让您生成并保存只有您的应用可以查看和使用的私钥。如需加载 Android 密钥库,请将 "AndroidKeyStore"
传递给 KeyStore.getInstance()
。
如需在 Android 密钥库中管理应用的私有凭据,请使用 KeyPairGenerator
和 KeyPairGeneratorSpec
生成一个新密钥。首先,通过调用 getInstance()
获取 KeyPairGenerator
的实例。然后调用 initialize()
,并向其传递 KeyPairGeneratorSpec
的实例,您可以使用 KeyPairGeneratorSpec.Builder
获取该实例。最后,通过调用 generateKeyPair()
获取 KeyPair
。
硬件凭据存储
Android 现在还支持由硬件支持的存储空间来存储您的 KeyChain
凭据,让密钥无法提取,从而提高安全性。也就是说,一旦密钥进入由硬件支持的密钥库(安全元件、TPM 或 TrustZone)中,就可以用于加密操作,但无法导出私钥材料。即使是操作系统内核也无法访问此密钥材料。虽然并非所有 Android 设备都支持硬件上的存储空间,但您可以通过调用 KeyChain.IsBoundKeyAlgorithm()
在运行时检查硬件支持的存储设备是否可用。
清单声明
可声明的必备功能
现在,<uses-feature>
元素中支持以下值,以便您确保仅安装在提供应用所需功能的设备上。
FEATURE_APP_WIDGETS
- 声明您的应用提供应用 widget,并且应仅安装在带有可供用户嵌入应用 widget 的主屏幕或类似位置的设备上。示例:
<uses-feature android:name="android.software.app_widgets" android:required="true" />
FEATURE_HOME_SCREEN
- 声明您的应用充当主屏幕替换应用,并且应仅安装在支持第三方主屏幕应用的设备上。示例:
<uses-feature android:name="android.software.home_screen" android:required="true" />
FEATURE_INPUT_METHODS
- 声明您的应用提供自定义输入法(使用
InputMethodService
构建的键盘),并且应仅安装在支持第三方输入法的设备上。 示例:<uses-feature android:name="android.software.input_methods" android:required="true" />
FEATURE_BLUETOOTH_LE
- 声明您的应用使用蓝牙低功耗 API,并且应仅安装在能够通过蓝牙低功耗与其他设备通信的设备上。
示例:
<uses-feature android:name="android.software.bluetooth_le" android:required="true" />
用户权限
<uses-permission>
现在支持以下值,以声明您的应用访问特定 API 所需的权限。
BIND_NOTIFICATION_LISTENER_SERVICE
- 使用新的
NotificationListenerService
API 是必需的。 SEND_RESPOND_VIA_MESSAGE
- 接收
ACTION_RESPOND_VIA_MESSAGE
intent 的必需项。
如需详细了解 Android 4.3 中的所有 API 变更,请参阅 API 差异报告。