Android 9(API 级别 28)向 Android 系统引入了多项变更。 当应用在 Android 9 平台上运行时,以下行为变更将影响所有应用,无论这些应用以哪个 API 级别为目标。 所有开发者都应查看这些变更,并修改其应用以正确支持这些变更(如果适用)。
如需了解仅影响以 API 28 或更高级别为目标的应用的变更,请参阅行为变更:以 API 级别 28+ 为目标的应用。
电源管理
Android 9 引入了新功能以改善设备电源管理。 这些变更连同 Android 9 之前已存在的功能可帮助确保系统资源被提供给最需要它们的应用。
详情请参阅电源管理。
隐私权变更
为了增强用户隐私,Android 9 引入了若干行为变更,如限制后台应用访问设备传感器、限制通过 Wi-Fi 扫描检索到的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。
无论采用哪一种目标 SDK 版本,这些变更都会影响运行于 Android 9 上的所有应用。
后台对传感器的访问受限
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
。
注:因为这些权限已变更组并在运行时授予,用户可以拒绝您的应用访问通话记录信息。 在这种情况下,您的应用应该能够妥善处理无法访问信息的状况。
如果您的应用已经遵循运行时权限最佳做法,则可以处理权限组的变更。
限制访问电话号码
在未首先获得 READ_CALL_LOG
权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9 上的应用无法读取电话号码或手机状态。
与来电和去电关联的电话号码可在手机状态广播(比如来电和去电的手机状态广播)中看到,并可通过 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 还需要在设备上启用位置服务(在 Settings > Location 下)。
从 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 的设备上停用设备定位,则以下函数不提供结果:
getAllCellInfo()
listen()
getCellLocation()
getNeighboringCellInfo()
对使用非 SDK 接口的限制
为帮助确保应用稳定性和兼容性,此平台对某些非 SDK 函数和字段的使用进行了限制;无论您是直接访问这些函数和字段,还是通过反射或 JNI 访问,这些限制均适用。 在 Android 9 中,您的应用可以继续访问这些受限的接口;该平台通过 toast 和日志条目提醒您注意这些接口。 如果您的应用显示这样的 toast,则必须寻求受限接口之外的其他实现策略。 如果您认为没有可行的替代策略,您可以提交错误以请求重新考虑此限制。
对非 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。 这些参数和许多算法的 Bouncy Castle 版本自 Android 9 起已被弃用。
注:EC 参数的 Conscrypt 实现仅支持已命名的曲线。
如果您的应用以 Android 8.1(API 级别 27)或更低版本为目标,则在请求这些已弃用算法之一的 Bouncy Castle 实现时,您将收到一条警告消息。 然而,如果您以 Android 9 为目标平台,则这些请求会各自引发 NoSuchAlgorithmException
。
其他变更
Android 9 引入了多项与加密有关的其他变更:
- 使用 PBE 密钥时,如果 Bouncy Castle 需要初始化矢量 (IV),而您的应用未提供 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”、“Etc/UTC”、“UTC”和“Zulu”之类时区的
android.icu
格式和解析行为。 java.text.SimpleDateFormat
现在使用 ICU 提供 UTC /GMT 的显示名称,这意味着:- 对于许多语言区域而言,设置
zzzz
的格式将会生成很长的本地化字符串。之前,对于 UTC 时区,它会生成“UTC”,而对于 GMT,则会生成“GMT+00:00”之类的字符串。 - 解析
zzzz
可识别“Universal Coordinated Time”和“Greenwich Mean Time”之类的字符串。 - 在所有语言里,如果应用接受“UTC”或“GMT+00:00”作为
zzzz
的输出,则可能会遇到兼容性问题。
- 对于许多语言区域而言,设置
java.text.DateFormatSymbols.getZoneStrings()
的行为已变更:- 与
SimpleDateFormat
类似,现在,UTC 和 GMT 也有长名称。对于 UTC 时区,DST 类型的时区名称(例如“UTC”、“Etc/UTC”和“Zulu”)变为 GMT+00:00(而不是硬编码字符串UTC
),这是在没有其他名称可用时的标准回退。 - 某些时区 ID 被正确地识别为其他地区的同义词,因此,Android 能够查找过时时区 ID(例如
Eire
)对应的字符串,而之前无法解决此问题。
- 与
- 亚洲/河内不再是可识别的时区。 因此,
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
类型的相应币种文本使用自 Android 7.0(API 级别 24)以来所提供的NumberFormat.parseCurrency
,可避免此问题。
Android Test 变更
Android 9 引入了多项针对 Android Test 框架库和类结构的更改。 这些变更可帮助开发者使用支持框架的公共 API,此外,在使用第三方库或自定义逻辑构建和运行测试时,这些变更还可提供更大的灵活性。
从框架移除的内容库
Android 9 将基于 JUnit 的类重新整理成三个内容库: android.test.base、android.test.runner 和 android.test.mock。 此变更允许您针对与您的项目依赖项搭配效果最好的 JUnit 版本运行测试。 此版本的 JUnit 可能不同于 android.jar
提供的版本。
如需了解有关如何将基于 JUnit 的类组织到这些内容库中,以及如何准备您的应用项目以编写和运行测试,请参阅针对 Android 测试设置项目。
测试套件版本号变更
移除了 TestSuiteBuilder
类中的 addRequirements()
函数,TestSuiteBuilder
类本身也已弃用。addRequirements()
函数要求开发者提供类型为隐藏 API 的参数,结果令 API 失效。
Java UTF 解码器
UTF-8 是 Android 中的默认字符集。 UTF-8 字节序列可由 String(byte[] bytes)
之类的 String
构造函数解码。
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 进程间通信将其发送给其他进程时,套接字会被取消标记。
在 Android 9 及更高版本中,利用 binder 进程间通信将套接字发送至其他进程时,其标记将得到保留。 此变更可能影响网络流量统计,例如,使用 queryDetailsForUidTag()
函数时。
如果您要保留以前版本的行为,即取消已发送至其他进程的套接字的标记,您可以在发送此套接字之前调用 untagSocket()
。
报告的套接字中可用字节数
在调用 shutdownInput()
函数后,available()
函数会在调用时返回 0
。
更详尽的 VPN 网络功能报告
在 Android 8.1(API 级别 28)及更低版本中,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 中,您不能从非 Activity 环境中启动 Activity,除非您传递 Intent 标志 FLAG_ACTIVITY_NEW_TASK
。 如果您尝试在不传递此标志的情况下启动 Activity,则该 Activity 不会启动,系统会在日志中输出一则消息。
注:在 Android 7.0(API 级别 24)之前,标志要求一直是期望的行为并被强制执行。 Android 7.0 中的一个错误会临时阻止实施标志要求。
屏幕旋转变更
从 Android 9 开始,对纵向旋转模式做出了重大变更。 在 Android 8.0(API 级别 26)中,用户可以使用 Quicksettings 图块或 Display 设置在自动屏幕旋转和纵向旋转模式之间切换。 纵向模式已重命名为旋转锁定,它会在自动屏幕旋转关闭时启用。 自动屏幕旋转模式没有任何变更。
当设备处于旋转锁定模式时,用户可将其屏幕锁定到顶层可见 Activity 所支持的任何旋转。 Activity 不应假定它将始终以纵向呈现。 如果顶层 Activity 可在自动屏幕旋转模式下以多种旋转呈现,则应在旋转锁定模式下提供相同的选项,根据 Activity 的 screenOrientation
设置,允许存在一些例外情况(见下表)。
请求特定屏幕方向(例如,screenOrientation=landscape
)的 Activity 会忽略用户锁定首选项,并且行为与 Android 8.0 中的行为相同。
可在 Android Manifest 中,或以编程方式通过 setRequestedOrientation() 在 Activity 级别设置屏幕方向首选项。
旋转锁定模式通过设置 WindowManager 在处理 Activity 旋转时使用的用户旋转首选项来发挥作用。 用户旋转首选项可能在下列情况下发生变更。 请注意,恢复设备的自然旋转存在偏差,对于外形与手机类似的设备通常设置为纵向:
- 当用户接受旋转建议时,旋转首选项变为建议方向。
- 当用户切换到强制纵向应用(包括锁定屏幕或启动器)时,旋转首选项变为纵向。
下表总结了常见屏幕方向的旋转行为:
屏幕方向 | 行为 |
---|---|
未指定、user | 在自动屏幕旋转和旋转锁定下,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()
发现每个可用的摄像头。 应用不应假定设备只有一个后置摄像头或只有一个前置摄像头。
例如,如果您的应用有一个用来切换前置和后置摄像头的按钮,则设备可能有多个前置或后置摄像头可供选择。 您应浏览一下摄像头列表,检查每个摄像头的特征,然后决定向用户显示哪些摄像头。