Android 9.0 (API 28) 适配
针对所有API级别的应用
电源管理
帮助确保系统资源被提供给最需要它们的应用,详情参阅电源管理
隐私权
-
后台对传感器的访问受限
App后台运行时,以下行为受限
访问麦克风和摄像头
使用连续报告模式的传感器(加速度计、陀螺仪等)不会接收事件
使用变化或一次性报告模式的传感器不会接收事件
-
限制访问通话记录
引入CALL_LOG权限组,把READ_CALL_LOG、WRIT_CALL_LOG、PROCESS_OUTGOING_CALLS移入该组(以前在PHONE权限组)。
-
限制访问电话号码
要从手机状态中读取电话号码,需要:
- 要通过PHONE_STATE Intent读取,需要READ_CALL_LOG和READ_PHONE_STATE权限
- 要通过
onCallStateChanged()
读取,只需要READ_CALL_LOG权限
-
限制访问WiFi位置和连接信息
- WiFi扫描限制更严格,详情参阅Wi-Fi 扫描限制
-
getConnectionInfo()
函数返回的WifiInfo
对象受限,只有当App具有以下权限时,才能获得SSID和BSSID:- ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
- ACCESS_WIFI_STATE
- 还需要在设备上启用位置服务(Settings > Location)
WiFi服务函数移除多余信息
在 Android 9 中,下列事件和广播不接收用户位置或个人可识别数据方面的信息:
WifiManager
中的getScanResults()
和getConnectionInfo()
函数。WifiP2pManager
中的 [discoverServices()
](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager.html?hl=zh-cn#discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener)) 和 [addServiceRequest()
](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager.html?hl=zh-cn#addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener)) 函数。NETWORK_STATE_CHANGED_ACTION
广播。Wi-Fi 的
NETWORK_STATE_CHANGED_ACTION
系统广播不再包含 SSID(之前为 EXTRA_SSID)、BSSID(之前为 EXTRA_BSSID)或连接信息(之前为 EXTRA_NETWORK_INFO)。 如果应用需要此信息,请改为调用getConnectionInfo()
。
-
电话信息依赖设备定位设置
如果停用设备定位,则以下函数不提供结果:
getAllCellInfo()
- [
listen()
](https://developer.android.com/reference/android/telephony/TelephonyManager.html?hl=zh-cn#listen(android.telephony.PhoneStateListener, int)) getCellLocation()
getNeighboringCellInfo()
non-sdk api的限制
为帮助确保应用稳定性和兼容性,此平台对某些非 SDK 函数和字段的使用进行了限制;无论您是直接访问这些函数和字段,还是通过反射或 JNI 访问,这些限制均适用。 在 Android 9 中,您的应用可以继续访问这些受限的接口;该平台通过 toast 和日志条目提醒您注意这些接口。 如果您的应用显示这样的 toast,则必须寻求受限接口之外的其他实现策略。 详情参阅对非 SDK 接口的限制
列表 | 说明 |
---|---|
黑名单 | 无论您应用的目标 API 级别是什么,您都无法使用此列表中的非 SDK 接口。如果您的应用尝试访问其中任何一个接口,系统就会抛出错误。 |
灰名单 | 只要在您应用的目标 API 级别不限制此列表中的非 SDK 接口,您就可以使用它们。 从 Android 9(API 级别 28)开始,我们在每个 API 级别分别会限制某些非 SDK 接口。如果您应用的目标 API 级别较低,您可以访问灰名单中的受限 API,但如果您的应用尝试访问在您的目标 API 级别受限的非 SDK 接口,系统就会假定此 API 已列入黑名单。 注意:在 Android 9(API 级别 28)中,非受限灰名单中的非 SDK 接口称为浅灰名单,而受限灰名单中的非 SDK 接口称为深灰名单。 |
白名单 | 此列表中的接口已在 Android 框架软件包索引中正式记录,它们是受支持的接口,您可以自由使用。 |
-
使用veridex工具测试
veridex 工具会扫描 APK 的整个代码库(包括所有第三方库),并报告发现的所有使用非 SDK 接口的行为。但是也有局限性:
- 无法检测到通过 JNI 实现的调用。
- 只能检测到一部分通过反射实现的调用。
- 对非活动代码路径的分析仅限于 API 级别的检查。
-
运行StrictMode API测试
使用
detectNonSdkApiUsage
方法来启用此 API。启用StrictMode
API 后,您可以使用 [penaltyListener
](https://developer.android.com/reference/android/os/StrictMode.VmPolicy.BuilderpenaltyListener(java.util.concurrent.Executor, android.os.StrictMode.OnVmViolationListener)?hl=zh-cn) 来接收每次使用非 SDK 接口的行为所对应的回调,并且您可以在其中实现自定义处理。回调中提供的Violation
对象派生自Throwable
,并且封闭式堆栈轨迹会提供相应使用行为的上下文。 -
使用可调试的应用测试
通过在搭载 Android 9(API 级别 28)或更高版本的设备或模拟器上构建和运行可调试应用来测试该应用是否使用非 SDK 接口。请确保您使用的设备或模拟器与您应用的目标 API 级别相匹配。
在您的应用上运行测试时,如果该应用访问了某些非 SDK 接口,系统就会输出一条日志消息。您可以检查应用的日志消息,查找以下详细信息:
- 声明的类、名称和类型(采用 Android 运行时所使用的格式)。
- 访问方式:链接、反射或 JNI
- 所访问的非 SDK 接口属于哪个列表。
您可以使用
adb logcat
来查看这些日志消息,这些消息显示在所运行应用的 PID 下。举例而言,日志中可能包含如下条目:Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
安全行为变更
设备安全性变更
传输层安全协议(TLS)实现变更
###### 更严格的SECCOMP过滤器
加密变更
参数和算法的 Conscrypt 实现
其他变更
不再支持Android安全加密文件
详情参阅安全行为变更
ICU更新
升级ICU库到60。受影响内容:
- 更好的区分GMT和UTC
- java.text.SimpleDateFormat的使用
- java.text.DateFormatSymbols.getZoneStrings()的使用
- 亚洲/河内不再是可识别的时区
- 使用NumberFormat.parseCurrency代替android.icu.text.NumberFormat.getInstance(ULocale, PLURALCURRENCYSTYLE).parse(String)去解析币种文本
详情参阅ICU更新
Android Test变更
详情参阅Android Test变更
Java UTF解码器
UTF-8 是 Android 中的默认字符集。 UTF-8 字节序列可由
String(byte[] bytes)
之类的String
构造函数解码。Android 9 中的 UTF-8 解码器遵循比以前版本中更严格的 Unicode 标准: 这些变更包括:
- 非最短形式的 UTF-8(例如
)被视为格式不正确。
- 替代形式的 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
的证书不再被信任。
网络地址查询可能导致网络违规
不运行在UI Thread中做网络请求,任何时候!
套接字标记
报告的套接字中可用字节数
在调用 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 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()
发现每个可用的摄像头。 应用不应假定设备只有一个后置摄像头或只有一个前置摄像头。
针对 Target >= 9.0 的应用
前台服务
针对 Android 9 或更高版本并使用前台服务的应用必须请求 FOREGROUND_SERVICE
权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。未请求权限就创建服务会引起SecurityException
隐私权变更
构建序列号弃用
在 Android 9 中,Build.SERIAL
始终设置为 "UNKNOWN"
以保护用户的隐私。如果您的应用需要访问设备的硬件序列号,您应改为请求 READ_PHONE_STATE
权限,然后调用 getSerial()
。
DNS 隐私
以 Android 9 为目标平台的应用应采用私有 DNS API。 具体而言,当系统解析程序正在执行 DNS-over-TLS 时,应用应确保任何内置 DNS 客户端均使用加密的 DNS 查找与系统相同的主机名,或停用它而改用系统解析程序。
框架安全性变更
默认情况下启用网络传输层安全协议 (TLS)
如果您的应用以 Android 9 或更高版本为目标平台,则默认情况下 isCleartextTrafficPermitted()
函数返回 false
。 如果您的应用需要为特定域名启用明文,您必须在应用的网络安全性配置中针对这些域名将 cleartextTrafficPermitted
显式设置为 true
按进程分设基于网络的数据目录
为改善 Android 9 中的应用稳定性和数据完整性,应用无法再让多个进程共用同一
WebView
数据目录。 此类数据目录一般存储 Cookie、HTTP 缓存以及其他与网络浏览有关的持久性和临时性存储。在大多数情况下,您的应用只应在一个进程中使用
android.webkit
软件包中的类,例如WebView
和CookieManager
。 例如,您应该将所有使用WebView
的Activity
对象移入同一进程。 您可以通过在应用的其他进程中调用disableWebView()
,更严格地执行“仅限一个进程”规则。 该调用可防止WebView
在这些其他进程中被错误地初始化,即使是从依赖内容库进行的调用也能防止。如果您的应用必须在多个进程中使用
WebView
的实例,则必须先利用WebView.setDataDirectorySuffix()
函数为每个进程指定唯一的数据目录后缀,然后再在该进程中使用WebView
的给定实例。 该函数会将每个进程的网络数据放入其在应用数据目录内自己的目录中。注:即使您使用
setDataDirectorySuffix()
,系统也不会跨应用的进程界限共享 Cookie 以及其他网络数据。 如果应用中的多个进程需要访问同一网络数据,您需要自行在这些进程之间复制数据。 例如,您可以调用getCookie()
和 [setCookie()
](https://developer.android.com/reference/android/webkit/CookieManager.html?hl=zh-cn#setCookie(java.lang.String, java.lang.String, android.webkit.ValueCallback)),在不同进程之间手动传输 Cookie 数据。
以应用为单位的 SELinux 域名
以 Android 9 或更高版本为目标平台的应用无法利用可全球访问的 Unix 权限与其他应用共享数据。 此变更可改善 Android 应用沙盒的完整性, 具体地讲,就是要求应用的私有数据只能由该应用访问。要与其他应用共享文件,请使用 content provider。
连接变更
连接数据计数和多路径
在以 Android 9 或更高版本为目标平台的应用中,系统计算并非当前默认网络的网络流量,例如,当设备连接 WLAN 时的蜂窝流量,并在
NetworkStatsManager
类中提供函数以查询该流量。具体而言,
getMultipathPreference()
现在将返回一个基于上述网络流量的值。 从 Android 9 开始,此函数针对蜂窝数据返回true
,但当超过一天内累积的特定流量时,它将开始返回false
。 在 Android 9 上运行的应用必须调用此函数并采用此提示。
ConnectivityManager.NetworkCallback
类现在将有关 VPN 的信息发送到应用。 此变更让应用侦听连接事件变得更容易,而无需混用同步和异步调用,也无需使用有限的 API。 此外,它还意味着将设备同时连接至多个 WLAN 网络或多个蜂窝网络时,信息传输可按预期工作。
Apache HTTP 客户端弃用
在 Android 6.0 中,我们取消了对 Apache HTTP 客户端的支持。 从 Android 9 开始,默认情况下该内容库已从 bootclasspath 中移除且不可用于应用。
要继续使用 Apache HTTP 客户端,以 Android 9 及更高版本为目标的应用可以向其
AndroidManifest.xml
添加以下内容:
注:拥有最低 SDK 版本 23 或更低版本的应用需要
android:required="false"
属性,因为在 API 级别低于 24 的设备上,org.apache.http.legacy
库不可用。 (在这些设备上,Apache HTTP 类在 bootclasspath 中提供。)作为使用运行时 Apache 库的替代,应用可以在其 APK 中绑定自己的
org.apache.http
库版本。 如果进行此操作,您必须将该库重新打包(使用一个类似 Jar Jar 的实用程序)以避免运行时中提供的类存在类兼容性问题。
界面变更
View Focus
0 面积的视图(即宽度或高度为 0)再也不能被聚焦。Activity 不再隐式分配触摸模式下的初始焦点,需要的话要显式请求初始焦点。
CSS RGBA 十六进制值处理
以 Android 9 或更高版本为目标的应用必须支持草案版 CSS 颜色模块级别 4 的行为,用于处理 4 和 8 个十六进制数字 CSS 颜色。
Chrome 自版本 52 以来便一直支持 CSS 颜色模块级别 4,但 WebView 目前停用此功能,因为现有 Android 应用被发现包含 Android ordering (ARGB) 中的 32 位十六进制颜色,这会导致渲染错误。
例如,对于以 API 级别 27 或更低版本为目标平台的应用,颜色
#80ff8080
目前在 WebView 中被渲染为不透明浅红色 (#ff8080
)。 先导部分(Android 会将其解读为 Alpha 部分)目前被忽略。 如果某个应用以 API 级别 28 或更高版本为目标,则#80ff8080
将被解读为 50% 透明浅绿 (#80ff80
)。
文档滚动标签
Android 9 可正确处理文档的根标签是滚动标签的案例。 在之前的版本中,滚动位置在 body 标签上设置,根标签的滚动值为零。 Android 9 支持符合标准的行为,在这种行为中,滚动标签是根标签。
此外,直接访问
document.body.scrollTop
、document.body.scrollLeft
、document.documentElement.scrollTop
或document.documentElement.scrollLeft
会因目标 SDK 的不同而具有不同的行为。 要访问视口滚动值,请使用document.scrollingElement
(若有)。
来自已暂停应用的通知
在 Android 9 之前,暂停的应用发出的通知会被取消。 从 Android 9 开始,暂停的应用发出的通知将被隐藏,直至应用继续运行。