Android 9(API 级别 28)向 Android 系统引入了多项变更。以下行为变更将影响在 Android 9 平台上运行的所有应用,无论其以哪个 API 级别为目标平台。所有开发者都应查看这些变更,并酌情修改其应用以适当地支持这些变更。
如需了解仅会影响以 API 级别 28 或更高级别为目标平台的应用的变更,请参阅行为变更:以 API 级别 28 及更高级别为目标平台的应用。
电源管理
Android 9 引入了新功能来改进设备电源管理。这些变更以及 Android 9 之前已有的功能有助于确保系统资源可供最需要它们的应用使用。
如需了解详情,请参阅电源管理。
隐私权变更
为了加强用户隐私保护,Android 9 引入了多项行为变更,例如限制后台应用对设备传感器的访问权限、限制从 Wi-Fi 扫描检索的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。
这些变更会影响在 Android 9 上运行的所有应用,无论目标 SDK 版本如何。
限制了在后台对传感器的访问权限
Android 9 限制了后台应用访问用户输入和传感器数据的能力。如果您的应用在搭载 Android 9 的设备上在后台运行,系统会对您的应用施加以下限制:
如果您的应用需要在搭载 Android 9 的设备上检测传感器事件,请使用前台服务。
限制了对通话记录的访问权限
Android 9 引入了 CALL_LOG
权限组,并将 READ_CALL_LOG
、WRITE_CALL_LOG
和 PROCESS_OUTGOING_CALLS
权限移到了此组。在较低版本的 Android 中,这些权限位于 PHONE
权限组中。
借助此 CALL_LOG
权限组,用户可以更好地控制和了解需要访问与通话相关的敏感信息(例如读取通话记录和识别电话号码)的应用。
如果您的应用需要访问通话记录或需要处理出站通话,您必须从 CALL_LOG
权限组中明确请求这些权限。否则,会发生 SecurityException
。
注意 :由于这些权限已更改组,并且是在运行时授予的,因此用户可能会拒绝向您的应用授予对通话记录信息的访问权限。在这种情况下,您的应用应能够妥善处理无法访问信息的情况。
如果您的应用已遵循运行时权限最佳实践,则可以处理权限组的更改。
限制了对电话号码的访问权限
在 Android 9 上运行的应用必须先获取 READ_CALL_LOG
权限(以及应用用例所需的其他权限),然后才能读取电话号码或手机状态。
与来电和去电关联的电话号码会显示在电话状态广播中,例如来电和去电,并且可通过 PhoneStateListener
类访问。不过,如果没有 READ_CALL_LOG
权限,PHONE_STATE_CHANGED
广播中和通过 PhoneStateListener
提供的电话号码字段将为空。
如需从手机状态读取电话号码,请更新您的应用,以便根据您的用例请求必要的权限:
- 如需从
PHONE_STATE
intent 操作读取数字,您需要同时拥有READ_CALL_LOG
权限和READ_PHONE_STATE
权限。 - 如需从
onCallStateChanged()
读取数字,您只需拥有READ_CALL_LOG
权限。您无需READ_PHONE_STATE
权限。
限制了对 Wi-Fi 位置信息和连接信息的访问权限
在 Android 9 中,与之前的版本相比,应用执行 Wi-Fi 扫描的权限要求更为严格。如需了解详情,请参阅 Wi-Fi 扫描限制。
类似限制也适用于 getConnectionInfo()
方法,该方法会返回一个 WifiInfo
对象来描述当前的 Wi-Fi 连接。只有当调用应用具有以下权限时,您才能使用此对象的方法检索 SSID 和 BSSID 值:
- ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
- ACCESS_WIFI_STATE
如需检索 SSID 或 BSSID,还需要在设备上启用位置信息服务(位于设置 > 位置信息下)。
从 Wi-Fi 服务方法中移除了信息
在 Android 9 中,以下事件和广播不会接收有关用户位置或个人身份数据的信息:
WifiManager
中的getScanResults()
和getConnectionInfo()
方法。WifiP2pManager
中的discoverServices()
和addServiceRequest()
方法。NETWORK_STATE_CHANGED_ACTION
广播。
通过 WLAN 接收的 NETWORK_STATE_CHANGED_ACTION
系统广播不再包含 SSID(以前称为 EXTRA_SSID)、BSSID(以前称为 EXTRA_BSSID)或连接信息(以前称为 EXTRA_NETWORK_INFO)。如果您的应用需要此信息,请改为调用 getConnectionInfo()
。
电话信息现在取决于设备位置信息设置
如果用户在搭载 Android 9 的设备上停用了设备位置信息,以下方法将不会提供结果:
使用非 SDK 接口的限制
为了帮助确保应用的稳定性和兼容性,平台限制使用某些非 SDK 方法和字段;无论您是尝试直接通过反射或使用 JNI 访问这些方法和字段,这些限制都会适用。在 Android 9 中,您的应用可以继续访问这些受限接口;平台会使用消息框和日志条目提醒您注意这些接口。如果您的应用显示此类消息框,请务必采用受限界面以外的实现策略。如果您认为没有可行的替代策略,可以提交bug 来请求重新考虑此限制。
针对非 SDK 接口的限制中包含更多重要信息。您应查看该报告,确保您的应用继续正常运行。
安全行为变更
设备安全性变更
Android 9 添加了多项可提升应用安全性的功能,无论应用以哪个版本为目标平台,这些功能都适用。
TLS 实现变更
Android 9 中,系统的 TLS 实现发生了一些变化:
- 如果
SSLSocket
实例在创建过程中未能连接,系统会抛出IOException
,而不是NullPointerException
。 SSLEngine
类会妥善处理发生的任何close_notify
提醒。
如需详细了解如何在 Android 应用中发出安全的 Web 请求,请参阅 HTTPS 示例。
更严格的 SECCOMP 过滤器
Android 9 进一步限制了可供应用使用的系统调用。此行为是对 Android 8.0(API 级别 26)包含的 SECCOMP 过滤器的扩展。
加密更改
Android 9 对加密算法的实现和处理进行了多项更改。
参数和算法的 Conscrypt 实现
Android 9 在 Conscrypt 中提供了算法参数的其他实现。这些参数包括:AES、DESEDE、OAEP 和 EC。从 Android 9 开始,这些参数和许多算法的 Bouncy Castle 版本已废弃。
如果您的应用以 Android 8.1(API 级别 27)或更低版本为目标平台,那么在请求其中一种已废弃算法的 Bouncy Castle 实现时,您会收到一条警告。不过,如果您的应用以 Android 9 为目标平台,则每个请求都会抛出 NoSuchAlgorithmException
。
其他变更
Android 9 引入了多项与加密有关的其他变更:
- 使用 PBE 密钥时,如果 Bouncy Castle 预期有初始化向量 (IV),而您的应用未提供,您会收到一条警告。
- 借助 ARC4 加密算法的 Conscrypt 实现,您可以指定 ARC4/ECB/NoPadding 或 ARC4/NONE/NoPadding。
- Crypto Java 加密架构 (JCA) 提供程序已被移除。因此,如果您的应用调用
SecureRandom.getInstance("SHA1PRNG", "Crypto")
,就会发生NoSuchProviderException
。 - 如果您的应用从大于密钥结构的缓冲区解析 RSA 密钥,则不会再发生异常。
如需详细了解如何使用 Android 的加密功能,请参阅加密。
Android 安全加密文件不再受支持
Android 9 完全移除了对 Android 安全加密文件 (ASEC) 的支持。
在 Android 2.2(API 级别 8)中,Android 引入了 ASEC 来支持 SD 卡上的应用功能。在 Android 6.0(API 级别 23)中,该平台引入了可采用的存储设备技术,开发者可以使用该技术来替代 ASEC。
ICU 库更新
Android 9 使用 ICU 库版本 60。Android 8.0(API 级别 26)和 Android 8.1(API 级别 27)使用 ICU 58。
ICU 用于在 android.icu package
下提供公共 API,并在 Android 平台内部用于提供国际化支持。例如,它用于在 java.util
、java.text
和 android.text.format
中实现 Android 类。
更新到 ICU 60 包含许多小而实用的变更,包括对 Emoji 5.0 数据的支持和改进的日期/时间格式,如 ICU 59 和 ICU 60 版本说明中所述。
本次更新中的显著变更:
- 该平台处理时区的方式已发生变化。
- 该平台更好地处理了 GMT 和 UTC;UTC 不再是 GMT 的同义词。
ICU 现在为 GMT 和 UTC 提供了经过翻译的时区名称。此更改会影响“GMT”“Etc/GMT”“UTC”“Etc/UTC”和“Zulu”等时区的
android.icu
格式设置和解析行为。 java.text.SimpleDateFormat
现在使用 ICU 为 UTC /GMT 提供显示名称,这意味着:- 对
zzzz
进行格式设置会为许多语言区域生成长长的本地化字符串。以前,它会为 UTC 生成“UTC”,并为 GMT 生成“GMT+00:00”等字符串。 - 解析
zzzz
时,系统会识别“世界协调时间”和“格林尼治标准时间”等字符串。 - 如果应用假定“UTC”或“GMT+00:00”是所有语言的
zzzz
输出,则可能会遇到兼容性问题。
- 对
java.text.DateFormatSymbols.getZoneStrings()
的行为已发生变化:- 与
SimpleDateFormat
一样,UTC 和 GMT 现在也有较长的名称。UTC 时区的时区名称的夏令时变体(例如“UTC”“Etc/UTC”和“Zulu”)会变为 GMT+00:00,这是在没有可用名称时采用的标准后备方案,而不是硬编码的字符串UTC
。 - 某些区域 ID 会被正确识别为其他区域的同义词,这样 Android 便可找到之前无法解析的旧版区域 ID(例如
Eire
)对应的字符串。
- 与
- Asia/Hanoi 不再是受支持的时区。因此,
java.util.TimeZones.getAvailableIds()
不会返回此值,并且java.util.TimeZone.getTimeZone()
也不会识别此值。此行为与现有的android.icu
行为一致。
- 该平台更好地处理了 GMT 和 UTC;UTC 不再是 GMT 的同义词。
- 即使解析合法的货币文本,
android.icu.text.NumberFormat.getInstance(ULocale, PLURALCURRENCYSTYLE).parse(String)
方法也可能会抛出ParseException
。如需避免此问题,请针对PLURALCURRENCYSTYLE
风格的货币文本使用NumberFormat.parseCurrency
(从 Android 7.0 [API 级别 24] 开始提供)。
Android 测试变更
Android 9 对 Android 测试框架的库和类结构进行了多项更改。这些更改有助于开发者使用框架支持的公共 API,但这些更改还让开发者能够更灵活地使用第三方库或自定义逻辑构建和运行测试。
从框架中移除了库
Android 9 将基于 JUnit 的类重新整理为三个库:android.test.base、android.test.runner 和 android.test.mock。通过这项更改,您可以针对与项目依赖项最适用的 JUnit 版本运行测试。此 JUnit 版本可能与 android.jar
提供的版本不同。
如需详细了解基于 JUnit 的类如何组织到这些库中,以及如何准备应用的项目以编写和运行测试,请参阅针对 Android Test 设置项目。
测试套件 build 变更
已移除 TestSuiteBuilder
类中的 addRequirements()
方法,并且 TestSuiteBuilder
类本身已被废弃。addRequirements()
方法要求开发者提供类型为隐藏 API 的参数,这会导致 API 无效。
Java UTF 解码器
UTF-8 是 Android 中的默认字符集。UTF-8 字节序列可以由 String
构造函数(例如 String(byte[] bytes)
)解码。
与之前的版本相比,Android 9 中的 UTF-8 解码器更严格地遵循 Unicode 标准。这些变更包括:
- 非最短形式的 UTF-8(例如
<C0, AF>
)会被视为格式错误。 - UTF-8 的代理形式(例如
U+D800
..U+DFFF
)会被视为格式错误。 - 最大子部分将替换为单个
U+FFFD
。例如,在字节序列“41 C0 AF 41 F4 80 80 41
”中,最大子部分是“C0
”“AF
”和“F4 80 80
”。F4 80 80
可以是F4 80 80 80
的初始子序列,但C0
不能是任何格式正确的代码单元序列的初始子序列。因此,输出应为“A\ufffd\ufffdA\ufffdA
”。 - 如需在 Android 9 或更高版本中解码经过修改的 UTF-8 / CESU-8 序列,请使用
DataInputStream.readUTF()
方法或NewStringUTF()
JNI 方法。
使用证书进行主机名验证
RFC 2818 介绍了将域名与证书进行匹配的两种方法:使用 subjectAltName
(SAN
) 扩展中的可用名称,或者在没有 SAN
扩展的情况下,回退到 commonName
(CN
)。
不过,RFC 2818 中已废弃回退到 CN
。因此,Android 不再回退到使用 CN
。如需验证主机名,服务器必须提供具有匹配 SAN
的证书。不包含与主机名匹配的 SAN
的证书不再受信任。
网络地址查找可能会导致网络违规
需要名称解析的网络地址查找可能涉及网络 I/O,因此被视为阻塞操作。在主线程上执行阻塞操作可能会导致应用暂停或卡顿。
StrictMode
类是一款开发工具,可帮助开发者检测代码中的问题。
在 Android 9 及更高版本中,StrictMode
会检测由需要名称解析的网络地址查找导致的网络违规行为。
您不应在启用 StrictMode
的情况下分发应用。如果您这样做,您的应用可能会遇到异常,例如在使用 detectNetwork()
或 detectAll()
方法获取用于检测网络违规行为的政策时出现 NetworkOnMainThreadException
。
解析数字 IP 地址不属于屏蔽操作。数字 IP 地址解析与 Android 9 之前的版本相同。
套接字代码植入
在低于 Android 9 的平台版本中,如果使用 setThreadStatsTag()
方法标记套接字,则在使用 ParcelFileDescriptor
容器通过 binder IPC 将套接字发送到另一个进程时,该套接字会取消标记。
在 Android 9 及更高版本中,当使用 binder IPC 将套接字标记发送到另一个进程时,系统会保留该标记。此更改可能会影响网络流量统计信息,例如在使用 queryDetailsForUidTag()
方法时。
如果您想保留先前版本的行为(即取消标记发送到其他进程的套接字),可以在发送套接字之前调用 untagSocket()
。
报告的套接字中可用字节数
在调用 shutdownInput()
方法后调用 available()
方法时,available()
方法会返回 0
。
针对 VPN 的网络功能报告更详细
在 Android 8.1(API 级别 27)及更低版本中,NetworkCapabilities
类仅针对 VPN 报告了有限的信息,例如 TRANSPORT_VPN
,但省略了 NET_CAPABILITY_NOT_VPN
。由于信息有限,因此很难确定使用 VPN 是否会导致应用用户产生费用。例如,检查 NET_CAPABILITY_NOT_METERED
无法确定底层网络是否计量流量。
在 Android 9 及更高版本中,当 VPN 调用 setUnderlyingNetworks()
方法时,Android 系统会合并任何底层网络的传输和功能,并将结果作为 VPN 网络的有效网络功能返回。
在 Android 9 及更高版本中,已检查 NET_CAPABILITY_NOT_METERED
的应用会收到 VPN 和底层网络的网络功能。
应用无法再使用 xt_qtaguid 文件夹中的文件
从 Android 9 开始,应用不得对 /proc/net/xt_qtaguid
文件夹中的文件拥有直接读取权限。原因是为了确保与一些根本不包含这些文件的设备保持一致。
依赖于这些文件(TrafficStats
和 NetworkStatsManager
)的公共 API 将继续按预期运行。不过,在不同设备上,不受支持的 cutils 函数(例如 qtaguid_tagSocket()
)可能无法按预期运行,甚至根本无法运行。
现在强制执行 FLAG_ACTIVITY_NEW_TASK 要求
在 Android 9 中,除非您传递 intent 标志 FLAG_ACTIVITY_NEW_TASK
,否则无法从非 activity 上下文启动 activity。如果您尝试在不传递此标志的情况下启动 activity,则 activity 不会启动,并且系统会向日志输出消息。
屏幕旋转更改
从 Android 9 开始,纵向旋转模式发生了重大变化。在 Android 8.0(API 级别 26)中,用户可以通过“快捷设置”功能块或“显示”设置,在自动旋转和纵向旋转模式之间切换。纵向模式已重命名为旋转锁定,它会在自动屏幕旋转模式关闭时启用。自动屏幕旋转模式没有任何变化。
当设备处于旋转锁定模式时,用户可以将其屏幕锁定为顶层可见 activity 支持的任何旋转方式。activity 不应假定它始终会以纵向模式呈现。如果顶层 activity 可在自动屏幕旋转模式下以多种旋转方向呈现,则旋转锁定模式下应提供相同的选项(根据 activity 的 screenOrientation
设置,也允许存在一些例外情况,请参阅下表)。
请求特定屏幕方向的 activity(例如 screenOrientation=landscape
)会忽略用户锁定偏好设置,并且行为与 Android 8.0 中的行为相同。
您可以在 Android 清单中的 activity 级别设置屏幕方向偏好设置,也可以使用 setRequestedOrientation() 以编程方式进行设置。
旋转锁定模式的运作方式是设置用户旋转偏好设置,WindowManager 在处理 activity 旋转时会使用该偏好设置。在以下情况下,用户旋转偏好设置可能会发生更改。请注意,系统会偏向于返回设备的自然屏幕方向,对于手机外形规格的设备,通常为纵向:
- 当用户接受旋转建议时,旋转偏好设置会更改为建议。
- 当用户切换到强制纵向的应用(包括锁屏或启动器)时,旋转偏好设置会更改为纵向。
下表总结了常见屏幕方向的旋转行为:
屏幕方向 | 行为 |
---|---|
未指定、用户 | 在自动屏幕旋转和旋转锁定模式下,Activity 可以以纵向或横向模式呈现(反之亦然)。预期同时支持纵向和横向布局。 |
userLandscape | 在自动旋转和旋转锁定模式下,activity 可以以横屏或反向横屏模式呈现。预期只支持横向布局。 |
userPortrait | 在自动屏幕旋转和旋转锁定模式下,activity 可以以纵向或反向纵向模式呈现。预期只支持纵向布局。 |
fullUser | 在自动屏幕旋转和旋转锁定模式下,Activity 可以以纵向或横向模式呈现(反之亦然)。应同时支持纵向和横向布局。 旋转锁定功能可让用户选择锁定为反向纵向(通常为 180º)。 |
sensor、fullSensor、sensorPortrait、sensorLandscape | 系统会忽略旋转锁定模式偏好设置,并将其视为自动屏幕旋转处于启用状态。请仅在例外情况下并经过仔细的用户体验考量后再使用此项。 |
Apache HTTP 客户端弃用会影响使用非标准 ClassLoader 的应用
在 Android 6.0 中,我们移除了对 Apache HTTP 客户端的支持。这项变更对未以 Android 9 或更高版本为目标平台的大多数应用没有影响。不过,此变更可能会影响使用非标准 ClassLoader
结构的某些应用,即使这些应用未以 Android 9 或更高版本为目标平台也是如此。
如果应用使用明确委托给系统 ClassLoader
的非标准 ClassLoader
,则可能会受到影响。在 org.apache.http.*
中查找类时,这些应用需要改为委托给应用
ClassLoader
。如果它们委托给系统 ClassLoader
,则在 Android 9 或更高版本中,应用将会失败并返回 NoClassDefFoundError
,因为系统 ClassLoader
不再知道这些类。为避免日后出现类似问题,应用通常应通过应用 ClassLoader
加载类,而不是直接访问系统 ClassLoader
。
枚举摄像头
在 Android 9 设备上运行的应用可以通过调用 getCameraIdList()
来发现每台可用摄像头。应用不应假定设备只有一个后置摄像头或只有一个前置摄像头。
例如,如果您的应用有一个用于在前置摄像头和后置摄像头之间切换的按钮,则可能有多个前置摄像头或后置摄像头可供选择。您应遍历相机列表,检查每个相机的特征,并决定向用户公开哪些相机。