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() 方法将 EXTRA_RESTRICTIONS_INTENT 与 intent 一起传递)。
当主用户进入您的 activity 以设置自定义限制时,您的 activity 必须返回一个结果,其中包含使用 EXTRA_RESTRICTIONS_LIST 或 EXTRA_RESTRICTIONS_BUNDLE 键在 extra 中指定的限制值,具体取决于您是分别指定 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 应用会使用此属性为受限个人资料停用自身,因为所有者的个人电子邮件不应可供受限个人资料使用。
无线和连接
低功耗蓝牙(智能就绪)
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 服务。
注意:您只能使用之前的 API 扫描蓝牙 LE 设备或传统蓝牙设备。您无法同时扫描 LE 和传统蓝牙设备。
然后,如需连接到蓝牙 LE 外围设备,请对相应的 BluetoothDevice 对象调用 connectGatt(),并向其传递 BluetoothGattCallback 的实现。您的 BluetoothGattCallback 实现会接收有关与设备的连接状态和其他事件的回调。如果该方法将 STATE_CONNECTED 作为新状态传递,您就可以在 onConnectionStateChange() 回调期间开始与设备通信。
如需在设备上访问蓝牙功能,您的应用还必须请求特定的蓝牙用户权限。如需了解详情,请参阅低功耗蓝牙 API 指南。
Wi-Fi 仅扫描模式
尝试确定用户的位置时,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 可让您更轻松地使用 MediaCodec 和 MediaExtractor 中的现有 API,根据 ISO/IEC 23009-1 标准编写自己的基于 HTTP 的动态自适应流式传输 (DASH) 播放器。这些 API 的底层框架已更新,可支持解析分段 MP4 文件,但您的应用仍负责解析 MPD 元数据并将各个数据流传递给 MediaExtractor。
如果您想将 DASH 与加密内容搭配使用,请注意,getSampleCryptoInfo() 方法会返回 MediaCodec.CryptoInfo 元数据,用于描述每个加密媒体样本的结构。此外,MediaExtractor 中添加了 getPsshInfo() 方法,以便您访问 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 纹理压缩作为标准功能
- 新版 GLSL ES 着色语言,支持整数和 32 位浮点
- 高级纹理渲染
- 更广泛地标准化纹理大小和渲染缓冲区格式
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 指南。
可绘制对象的 Mipmapping
使用 mipmap 作为位图或可绘制对象的来源,是提供优质图片和各种图片缩放比例的简单方法,如果您希望图片在动画期间缩放,这种方法会特别有用。
Android 4.2(API 级别 17)在 Bitmap 类中添加了对 mipmap 的支持 - 如果您提供了 mipmap 来源并启用了 setHasMipMap(),Android 会在 Bitmap 中交换 mip 图片。现在,在 Android 4.3 中,您还可以为 BitmapDrawable 对象启用 mipmap,方法是提供 mipmap 资源并在位图资源文件中设置 android:mipMap 属性,或调用 hasMipMap()。
界面
查看叠加层
新的 ViewOverlay 类在 View 之上提供了一个透明层,您可以在该层上添加视觉内容,并且该层不会影响布局层次结构。您可以通过调用 getOverlay() 为任何 View 获取 ViewOverlay。叠加层的大小和位置始终与其宿主视图(创建它的视图)相同,这样您就可以添加显示在宿主视图前面的内容,但这些内容不能超出该宿主视图的边界。
当您想要创建动画(例如将视图滑出其容器或在屏幕上移动项目)而不影响视图层次结构时,使用 ViewOverlay 特别有用。不过,由于叠加层的可用区域仅限于其宿主视图所在的区域,因此,如果您想为视图在布局中的位置之外移动添加动画效果,则必须使用具有所需布局边界的父视图中的叠加层。
为 widget 视图(例如 Button)创建叠加层时,您可以通过调用 add(Drawable) 向叠加层添加 Drawable 对象。如果您针对布局视图(例如 RelativeLayout)调用 getOverlay(),则返回的对象为 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 使用的是光学边界。由于用于按钮和相框的九宫格图片在边缘周围包含内边距,因此在使用剪辑边界时,它们似乎不会相互对齐或与文本对齐。
注意:图 1 和图 2 中的屏幕截图启用了“显示布局边界”开发者设置。对于每个视图,红线表示光学边界,蓝线表示裁剪边界,粉色表示外边距。
图 1. 使用剪辑边界的布局(默认)。
图 2. 使用光学边界的布局。
如需根据视图的光学边界对齐视图,请在其中一个父布局中将 android:layoutMode 属性设置为 "opticalBounds"。例如:
<LinearLayout android:layoutMode="opticalBounds" ... >
图 3. 具有光学边界的 Holo 按钮 9-patch 的放大视图。
为此,应用于视图背景的九宫格图片必须使用沿九宫格文件底部和右侧的红线指定光学边界(如图 3 所示)。红线表示应从裁剪边界中减去的区域,从而留下图片的视觉边界。
当您为布局中的 ViewGroup 启用光学边界时,所有后代视图都会继承光学边界布局模式,除非您通过将 android:layoutMode 设置为 "clipBounds" 来为某个组替换该模式。所有布局元素也会遵循其子视图的光学边界,并根据其内部视图的光学边界调整自己的边界。不过,布局元素(ViewGroup 的子类)目前不支持对应用于其自身背景的九宫格图片使用光学边界。
如果您通过将 View、ViewGroup 或其任何子类设为子类来创建自定义视图,则您的视图将继承这些光学边界行为。
注意:Holo 主题支持的所有微件都已更新为光学边界,包括 Button、Spinner、EditText 等。因此,如果您的应用应用了 Holo 主题(Theme.Holo、Theme.Holo.Light 等),您可以通过将 android:layoutMode 属性设置为 "opticalBounds" 立即受益。
如需使用 Draw 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”
新的联系人提供程序查询 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,可帮助您正确设置包含相反方向内容的文本的格式,而不会导致任何部分出现乱码。
例如,如果您想要创建一个具有字符串变量的句子,如“Did you mean 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,可让 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 还可通过 Android 支持库(使用 AccessibilityNodeInfoCompat 类)用于以前版本的 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 中的 API 不同。
应用的 Systrace 事件
Android 4.3 添加了 Trace 类,其中包含两个静态方法:beginSection() 和 endSection(),可让您定义要包含在 systrace 报告中的代码块。通过在应用中创建可跟踪的代码部分,systrace 日志可为您提供更详细的分析,帮助您了解应用中哪些位置发生了运行缓慢的情况。
如需了解如何使用 Systrace 工具,请参阅使用 Systrace 分析显示和性能。
安全
用于存储应用私钥的 Android 密钥库
Android 现在在 KeyStore 设施中提供了一个自定义 Java 安全提供程序,名为 Android 密钥库,可让您生成和保存只能由您的应用查看和使用的私钥。如需加载 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- 必须使用新的
NotificationListenerServiceAPI。 SEND_RESPOND_VIA_MESSAGE- 接收
ACTION_RESPOND_VIA_MESSAGEintent 所必需。
如需详细了解 Android 4.3 中的所有 API 变更,请参阅 API 差异报告。