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()
方法,该方法返回描述当前 Wi-Fi 连接的 WifiInfo
对象。只有当调用应用具有以下权限时,您才能使用此对象的方法检索 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
广播。
Wi-Fi 的 NETWORK_STATE_CHANGED_ACTION
系统广播不再包含 SSID(以前是 EXTRA_SSID)、BSSID(以前是 EXTRA_BSSID)或连接信息(以前是 EXTRA_NETWORK_INFO)。如果您的应用需要这些信息,请改用 getConnectionInfo()
。
电话信息现在依赖于设备位置设置
如果用户在运行 Android 9 的设备上禁用了设备位置信息,则以下方法不提供结果:
非 SDK 接口使用限制
为了帮助确保应用的稳定性和兼容性,平台限制了某些非 SDK 方法和字段的使用;这些限制适用于您尝试直接、通过反射或使用 JNI 访问这些方法和字段的情况。在 Android 9 中,您的应用可以继续访问这些受限接口;平台会通过浮动提示和日志条目提醒您。如果您认为没有可行的替代策略,您可以提交一个错误,请求重新考虑限制。
非 SDK 接口限制包含更多重要信息。您应该仔细阅读它,以确保您的应用继续正常运行。
安全行为变更
设备安全变更
Android 9 添加了几项功能,可以提高您应用的安全性,无论您的应用面向哪个版本。
TLS 实现变更
系统 TLS 实现在 Android 9 中经历了几项变更:
- 如果
SSLSocket
实例在创建时连接失败,系统会抛出IOException
而不是NullPointerException
。 SSLEngine
类能够干净地处理发生的任何close_notify
警报。
要了解如何在 Android 应用中发出安全的网络请求,请参阅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
可以识别“协调世界时”和“格林威治标准时间”等字符串。 - 如果应用假定所有语言的
zzzz
输出都是“UTC”或“GMT+00:00”,则可能会遇到兼容性问题。
- 格式化
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
。通过使用自 Android 7.0(API 级别 24)以来可用的NumberFormat.parseCurrency
来处理PLURALCURRENCYSTYLE
风格的货币文本,可避免此问题。
Android 测试变更
Android 9 引入了 Android Test 框架库和类结构的一些变更。这些变更帮助开发者使用框架支持的公共 API,但这些变更也允许在使用第三方库或自定义逻辑构建和运行测试时具有更大的灵活性。
从框架中移除的库
Android 9 将基于 JUnit 的类重新组织到三个库中:android.test.base、android.test.runner 和 android.test.mock。此更改允许您针对最适合您的项目依赖项的 JUnit 版本运行测试。此 JUnit 版本可能与 android.jar
提供的版本不同。
要了解有关基于 JUnit 的类如何组织到这些库中以及如何为编写和运行测试准备应用的项目的更多信息,请参阅设置 Android Test 项目。
测试套件构建变更
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
)。
然而,回退到 CN
已在 RFC 2818 中弃用。因此,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()
方法返回 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
文件夹中的文件。原因是确保与某些完全没有这些文件的设备保持一致。
依赖这些文件的公共 API,TrafficStats
和 NetworkStatsManager
,将继续按预期工作。然而,不受支持的 cutils 函数,例如 qtaguid_tagSocket()
,在不同设备上可能无法按预期工作——甚至完全无法工作。
现在强制执行 FLAG_ACTIVITY_NEW_TASK 要求
从 Android 9 开始,除非您传递意图标志 FLAG_ACTIVITY_NEW_TASK
,否则无法从非 Activity 上下文启动 Activity。如果您尝试在不传递此标志的情况下启动 Activity,Activity 将不会启动,系统会向日志打印一条消息。
屏幕旋转变更
从 Android 9 开始,竖屏旋转模式发生了重大变化。在 Android 8.0(API 级别 26)中,用户可以使用快速设置图块或显示设置在自动旋转和竖屏旋转模式之间切换。竖屏模式已更名为旋转锁定,并在自动旋转关闭时激活。自动旋转模式没有变化。
当设备处于旋转锁定模式时,用户可以将其屏幕锁定到顶部可见 Activity 支持的任何旋转方向。Activity 不应假定它将始终以竖屏模式呈现。如果顶级 Activity 可以在自动旋转模式下以多种旋转方向呈现,则在旋转锁定模式下也应提供相同的选项,但根据 Activity 的 screenOrientation
设置会有一些例外(请参阅下表)。
请求特定方向(例如,screenOrientation=landscape
)的 Activity 会忽略用户锁定偏好设置,其行为与 Android 8.0 中相同。
屏幕方向偏好设置可以在 Android Manifest 中的 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()
来发现每个可用的相机。应用不应假设设备只有单个后置摄像头或单个前置摄像头。
例如,如果您的应用有一个按钮用于在前后摄像头之间切换,则可能有多个前置或后置摄像头可供选择。您应该遍历摄像头列表,检查每个摄像头的特性,并决定向用户公开哪些摄像头。