针对 Android 7.0 开发的应用

针对 Android 7.0 开发的应用

除了新特性和功能外,Android 7.0 还包含多种系统和 API 行为变更。本文档重点介绍了您应当了解并在应用中加以考虑的一些关键变更。

如果您之前曾发布过 Android 应用,请注意您的应用可能会受到这些平台变更的影响。

电池与内存

Android 7.0 包含旨在改善设备电池续航时间并减少 RAM 使用量的系统行为变更。这些变更可能会影响您的应用对系统资源的访问,以及应用通过某些隐式意图与其他应用交互的方式。

低电耗模式 (Doze)

Android 6.0(API 23 级别)中引入的低电耗模式通过在用户未插电、保持静止且屏幕关闭时推迟 CPU 和网络活动来改善电池续航时间。Android 7.0 对低电耗模式进行了进一步增强:当设备未插电且屏幕关闭(但不一定处于静止状态,例如手机放在用户的口袋里随身携带)时,系统会应用部分 CPU 和网络限制。

图 1. 说明低电耗模式如何应用第一级系统活动限制以改善电池续航时间。

当设备由电池供电且屏幕关闭一段时间后,设备会进入低电耗模式并应用第一组限制:关闭应用的网络访问,并推迟作业和同步。如果设备进入低电耗模式后保持静止一段时间,系统会为 PowerManager.WakeLock、AlarmManager 闹钟、GPS 和 Wi-Fi 扫描应用其余的低电耗限制。无论是否应用了全部或部分低电耗限制,系统都会在短暂的维护窗口内唤醒设备,在此期间允许应用访问网络并执行任何推迟的作业/同步。

图 2. 说明设备保持静止一段时间后,低电耗模式如何应用第二级系统活动限制。

请注意,点亮屏幕或为设备充电会退出低电耗模式并解除这些处理限制。这种额外行为不会影响您针对 Android 6.0(API 23 级别)中引入的早期版本低电耗模式所采取的建议和最佳实践,详见 针对低电耗模式和应用待机模式进行优化。您仍应遵循这些建议(例如使用 Firebase 云消息传递 (FCM) 发送和接收消息),并开始规划更新以适应额外的低电耗模式行为。

Project Svelte:后台优化

Android 7.0 移除了三项隐式广播,以帮助优化内存使用和功耗。此项变更是必要的,因为隐式广播经常会启动在后台注册监听这些广播的应用。移除这些广播可以显著改善设备性能和用户体验。

移动设备经常会遇到连接变更,例如在 Wi-Fi 和移动数据之间切换时。目前,应用可以通过在其清单文件中注册一个隐式 CONNECTIVITY_ACTION 广播接收器来监控连接变更。由于许多应用都会注册接收此广播,因此一次网络切换可能会导致所有应用同时被唤醒并处理该广播。

同样,在之前版本的 Android 中,应用可以注册以接收来自相机等其他应用的隐式 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 广播。当用户使用相机应用拍摄照片时,这些应用会被唤醒以处理广播。

为了缓解这些问题,Android 7.0 应用了以下优化措施:

如果应用在清单中声明了广播接收器,则针对 Android 7.0(API 24 级别)及更高版本开发的应用不会接收 CONNECTIVITY_ACTION 广播。如果应用通过 Context.registerReceiver() 注册其 BroadcastReceiver 且该上下文仍然有效,它们仍将接收 CONNECTIVITY_ACTION 广播。

系统不再发送 ACTION_NEW_PICTURE 或 ACTION_NEW_VIDEO 广播。此项优化影响所有应用,而不仅仅是针对 Android 7.0 开发的应用。

如果您的应用使用了这些意图,您应尽快移除对它们的依赖,以便能够正确适配 Android 7.0 设备。Android 框架提供了多种解决方案来降低对这些隐式广播的需求。例如,JobScheduler API 提供了一种强大的机制,可以在满足特定条件(如连接到不限流量的网络)时调度网络操作。您甚至可以使用 JobScheduler 来响应内容提供程序的变更。

有关 Android 7.0(API 24 级别)后台优化的详细信息以及如何适配您的应用,请参阅 后台优化。

权限变更

Android 7.0 包含可能影响您的应用的权限变更。

文件系统权限变更

为了提高私有文件的安全性,针对 Android 7.0 或更高版本开发的应用其私有目录的访问权限受到限制 (0700)。此设置可防止私有文件的元数据(如其大小或存在性)泄漏。此权限变更具有多种副作用:

私有文件的权限不应再由所有者放宽;尝试使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 放宽权限将会触发 SecurityException。

注意:目前,此限制尚未完全强制执行。应用仍可以使用原生 API 或 File API 修改其私有目录的权限。但是,我们强烈不建议放宽私有目录的权限。

在软件包域外传递 file:// URI 可能会导致接收方获取到无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。共享私有文件内容的推荐方式是使用 FileProvider。

DownloadManager 不能再通过文件名共享私有存储的文件。旧版应用在访问 COLUMN_LOCAL_FILENAME 时可能会得到无法访问的路径。针对 Android 7.0 或更高版本开发的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。通过使用 DownloadManager.Request.setDestinationInExternalFilesDir() 或 DownloadManager.Request.setDestinationInExternalPublicDir() 将下载位置设置为公共位置的旧版应用仍可以访问 COLUMN_LOCAL_FILENAME 中的路径,但强烈不建议使用此方法。访问由 DownloadManager 公开的文件的首选方式是使用 ContentResolver.openFileDescriptor()。

应用间的文件共享

对于针对 Android 7.0 开发的应用,Android 框架强制执行 StrictMode API 策略,禁止在应用外公开 file:// URI。如果包含文件 URI 的意图离开您的应用,应用将因抛出 FileUriExposedException 异常而失败。

要在应用间共享文件,您应该发送 content:// URI 并授予该 URI 的临时访问权限。授予此权限的最简单方法是使用 FileProvider 类。有关权限和文件共享的详细信息,请参阅 共享文件。

辅助功能改进

Android 7.0 包含旨在改善低视力或视障用户平台可用性的变更。这些变更通常不需要对您的应用进行代码修改,但您应该检查这些功能并使用您的应用进行测试,以评估对用户体验的潜在影响。

屏幕缩放

Android 7.0 允许用户设置显示尺寸,从而放大或缩小屏幕上的所有元素,进而提高低视力用户的设备可用性。用户无法将屏幕缩放到小于 sw320dp(Nexus 4 的宽度,一种常见的中型手机)的屏幕宽度。

图 3. 右侧屏幕显示了在运行 Android 7.0 系统映像的设备上增加显示尺寸的效果。

当设备密度发生变化时,系统会通过以下方式通知正在运行的应用:

如果应用针对 API 23 或更低级别开发,系统会自动终止其所有后台进程。这意味着,如果用户切换离开此类应用以打开设置屏幕并更改显示尺寸设置,系统会像在低内存情况下一样终止该应用。如果应用有任何前台进程,系统会按照 处理运行时变更 中所述通知这些进程配置更改,就像设备方向发生变化一样。

如果应用针对 Android 7.0 开发,则其所有进程(前台和后台)都会按照 处理运行时变更 中所述收到配置更改的通知。

只要遵循 Android 最佳实践,大多数应用无需进行任何更改即可支持此功能。需要检查的具体事项:

在屏幕宽度为 sw320dp 的设备上测试您的应用,并确保其表现良好。

当设备配置发生变化时,更新任何依赖于密度的缓存信息,例如缓存的位图或从网络加载的资源。当应用从暂停状态恢复时,检查配置变更。

注意:如果您缓存了依赖于配置的数据,最好包含相关的元数据,例如该数据适用的屏幕尺寸或像素密度。保存此元数据可以让您确定在配置变更后是否需要刷新缓存的数据。

避免使用 px 单位指定尺寸,因为它们不会随屏幕密度缩放。相反,应使用密度无关像素 (dp) 单位指定尺寸。

设置向导中的视觉设置

Android 7.0 在欢迎屏幕中包含了“视觉设置”,用户可以在新设备上设置以下辅助功能:放大手势、字体大小、显示尺寸和 TalkBack。此项变更增加了与不同屏幕设置相关的错误可见性。为了评估此功能的影响,您应该在启用这些设置的情况下测试您的应用。您可以在设置 > 辅助功能下找到这些设置。

链接到平台库的 NDK 应用

从 Android 7.0 开始,系统禁止应用动态链接到非 NDK 库,这可能会导致您的应用崩溃。此行为变更旨在跨平台更新和不同设备之间建立一致的应用体验。即使您的代码可能没有链接到私有库,应用中的第三方静态库也可能这样做。因此,所有开发者都应检查并确保他们的应用在运行 Android 7.0 的设备上不会崩溃。如果您的应用使用原生代码,则只能使用公共 NDK API。

您的应用可能通过以下三种方式尝试访问私有平台 API:

您的应用直接访问私有平台库。您应该更新应用,以包含这些库的自身副本,或使用公共 NDK API。

您的应用使用的第三方库访问了私有平台库。即使您确定自己的应用没有直接访问私有库,您也应该针对这种情况测试您的应用。

您的应用引用的库未包含在其 APK 中。例如,如果您尝试使用自己的 OpenSSL 副本但忘记将其与应用 APK 捆绑,就会发生这种情况。应用在包含 libcrypto.so 的 Android 平台版本上可能运行正常,但在不包含此库的后续 Android 版本(如 Android 6.0 及更高版本)上可能会崩溃。要解决此问题,请确保将所有非 NDK 库与您的 APK 捆绑在一起。

应用不应使用未包含在 NDK 中的原生库,因为它们在不同版本的 Android 之间可能会发生变化或被移除。从 OpenSSL 切换到 BoringSSL 就是此类变更的一个例子。此外,由于对于未包含在 NDK 中的平台库没有兼容性要求,不同设备可能提供不同程度的兼容性。

为了减少此限制可能对当前已发布应用造成的影响,在 Android 7.0(API 24 级别)中,针对 API 23 或更低级别开发的应用可暂时访问一组使用量较大的库,例如 libandroid_runtime.so、libcutils.so、libcrypto.so 和 libssl.so。如果您的应用加载了其中一个库,logcat 会生成一条警告,并且目标设备上会显示一个 Toast 消息来通知您。如果您看到这些警告,则应更新您的应用,要么包含这些库的自身副本,要么仅使用公共 NDK API。Android 平台的未来版本可能会完全限制对私有库的使用,并导致您的应用崩溃。

所有应用在调用既非公共也非临时可访问的 API 时都会生成运行时错误。结果是 System.loadLibrary 和 dlopen(3) 都会返回 NULL,并可能导致您的应用崩溃。您应该审查应用代码以移除对私有平台 API 的使用,并使用运行 Android 7.0(API 24 级别)的设备或模拟器彻底测试您的应用。如果您不确定应用是否使用了私有库,可以检查 logcat 以识别运行时错误。

下表描述了根据应用对私有原生库的使用情况及其目标 API 级别 (android:targetSdkVersion) 所表现出的预期行为。

目标 API 级别

通过动态链接器的运行时访问

Android 7.0(API 24 级别)行为

未来 Android 平台行为

NDK 公共

任何

可访问

按预期工作

按预期工作

私有(临时可访问的私有库)

23 或更低

暂时可访问

按预期工作,但您会收到 logcat 警告。

运行时错误

私有(临时可访问的私有库)

24 或更高

受限

运行时错误

运行时错误

私有(其他)

任何

受限

运行时错误

运行时错误

检查您的应用是否使用了私有库

为了帮助您识别加载私有库的问题,logcat 可能会生成警告或运行时错误。例如,如果您的应用针对 API 23 或更低级别开发,并且尝试在运行 Android 7.0 的设备上访问私有库,您可能会看到类似于以下的警告:

03-21 17:07:51.502 31234 31234 W linker : library "libandroid_runtime.so"

("/system/lib/libandroid_runtime.so") needed or dlopened by

"/data/app/com.popular-app.android-2/lib/arm/libapplib.so" is not accessible

for the namespace "classloader-namespace" - the access is temporarily granted

as a workaround for http://b/26394120

这些 logcat 警告会告知您哪个库试图访问私有平台 API,但不会导致您的应用崩溃。但是,如果应用针对 API 24 或更高级别开发,logcat 会生成以下运行时错误,并且您的应用可能会崩溃:

java.lang.UnsatisfiedLinkError: dlopen failed: library "libcutils.so"

("/system/lib/libcutils.so") needed or dlopened by

"/system/lib/libnativeloader.so" is not accessible for the namespace

"classloader-namespace"

at java.lang.Runtime.loadLibrary0(Runtime.java:977)

at java.lang.System.loadLibrary(System.java:1602)

如果您的应用使用了动态链接到私有平台 API 的第三方库,您也可能会看到这些 logcat 输出。Android 7.0 NDK 中的 readelf 工具允许您通过运行以下命令来生成给定 .so 文件的所有动态链接共享库的列表:

aarch64-linux-android-readelf -dW libMyLibrary.so

更新您的应用

以下是您可以采取的一些步骤,以修复这些类型的错误并确保您的应用在未来的平台更新中不会崩溃:

如果您的应用使用了私有平台库,您应该更新它以包含这些库的自身副本,或使用公共 NDK API。

如果您的应用使用了访问私有符号的第三方库,请联系库作者进行更新。

确保将所有非 NDK 库与您的 APK 捆绑在一起。

使用标准 JNI 函数,而不是 libandroid_runtime.so 中的 getJavaVM 和 getJNIEnv。AndroidRuntime::getJavaVM -> GetJavaVM from

AndroidRuntime::getJNIEnv -> JavaVM::GetEnv or

JavaVM::AttachCurrentThread from .

使用 __system_property_get 代替 libcutils.so 中的私有 property_get 符号。为此,请使用包含以下内容的 __system_property_get:#include

注意:系统属性的可用性和内容未通过 CTS 测试。更好的修复方法是完全避免使用这些属性。

使用 libcrypto.so 中 SSL_ctrl 符号的本地版本。例如,您应该在 .so 文件中静态链接 libcrypto.a,或者包含来自 BoringSSL/OpenSSL 的动态链接版本 libcrypto.so 并将其打包在您的 APK 中。

Android for Work

Android 7.0 包含针对 Android for Work 开发的应用的变更,包括证书安装、密码重置、辅助用户管理和设备标识符访问方面的变更。如果您正在为 Android for Work 环境构建应用,则应审查这些变更并相应地修改您的应用。

在 DPC 设置委托证书安装程序之前,您必须先安装它。对于针对 Android 7.0(API 24 级别)开发的文件所有者和设备所有者应用,您应该在设备策略控制器 (DPC) 调用 DevicePolicyManager.setCertInstallerPackage() 之前安装委托证书安装程序。如果尚未安装安装程序,系统会抛出 IllegalArgumentException。

设备管理员的重置密码限制现在适用于配置文件所有者。设备管理员不能再使用 DevicePolicyManager.resetPassword() 来清除密码或更改已设置的密码。设备管理员仍然可以设置密码,但前提是设备当前没有密码、PIN 码或图案。

即使设置了限制,设备所有者和配置文件所有者也可以管理帐户。即使启用了 DISALLOW_MODIFY_ACCOUNTS 用户限制,设备所有者和配置文件所有者也可以调用帐户管理 API。

设备所有者可以更轻松地管理辅助用户。当设备以设备所有者模式运行时,会自动设置 DISALLOW_ADD_USER 限制。这可以防止用户创建非托管的辅助用户。此外,CreateUser() 和 createAndInitializeUser() 方法已被弃用;新的 DevicePolicyManager.createAndManageUser() 方法取代了它们。

设备所有者可以访问设备标识符。设备所有者可以使用 DevicePolicyManager.getWifiMacAddress() 访问设备的 Wi-Fi MAC 地址。如果设备上从未启用过 Wi-Fi,则此方法返回 null。

工作模式设置控制对工作应用的访问。当工作模式关闭时,系统启动器会通过将工作应用置灰来指示它们不可用。重新启用工作模式将恢复正常行为。

当通过设置界面安装包含客户端证书链和相应私钥的 PKCS #12 文件时,链中的 CA 证书不再安装到受信任的凭据存储中。这不会影响应用稍后尝试检索客户端证书链时 KeyChain.getCertificateChain() 的结果。如果需要,应通过设置界面单独将 CA 证书安装到受信任的凭据存储中,格式为带有 .crt 或 .cer 文件扩展名的 DER 编码。

从 Android 7.0 开始,指纹录入和存储按用户管理。如果配置文件所有者的设备策略客户端 (DPC) 在运行 Android 7.0(API 24 级别)的设备上针对 API 级别 23(或更低)开发,用户仍然可以在设备上设置指纹,但工作应用无法访问设备指纹。当 DPC 针对 API 24 及更高版本开发时,用户可以通过转到设置 > 安全 > 工作资料安全专门为工作资料设置指纹。

DevicePolicyManager.getStorageEncryptionStatus() 会返回一个新的加密状态 ENCRYPTION_STATUS_ACTIVE_PER_USER,以指示加密处于活动状态且加密密钥与用户绑定。此新状态仅在 DPC 针对 API 24 及更高版本开发时才会返回。对于针对早期 API 级别开发的应用,即使加密密钥特定于用户或资料,也会返回 ENCRYPTION_STATUS_ACTIVE。

在 Android 7.0 中,如果设备安装了具有单独工作挑战的工作资料,则通常影响整个设备的若干方法会有不同的行为。这些方法不再影响整个设备,而是仅应用于工作资料。(此类方法的完整列表位于 DevicePolicyManager.getParentProfileInstance() 文档中。)例如,DevicePolicyManager.lockNow() 仅锁定工作资料,而不是锁定整个设备。对于这些方法中的每一个,您可以通过在 DevicePolicyManager 的父实例上调用该方法来获得旧的行为;您可以通过调用 DevicePolicyManager.getParentProfileInstance() 获取此父实例。因此,例如,如果您调用父实例的 lockNow() 方法,则整个设备会被锁定。

注解保留

Android 7.0 修复了一个注解可见性被忽略的错误。此问题允许运行时访问其本不应访问的注解。这些注解包括:

VISIBILITY_BUILD:仅在构建时可见。

VISIBILITY_SYSTEM:在运行时可见,但仅对底层系统可见。

如果您的应用依赖此行为,请为必须在运行时可用的注解添加保留策略。您可以使用 @Retention(RetentionPolicy.RUNTIME) 来实现。

TLS/SSL 默认配置变更

Android 7.0 对应用用于 HTTPS 和其他 TLS/SSL 流量的默认 TLS/SSL 配置进行了以下变更:

RC4 加密套件现已被禁用。

CHACHA20-POLY1305 加密套件现已被启用。

默认禁用 RC4 可能会导致 HTTPS 或 TLS/SSL 连接中断(当服务器不协商现代加密套件时)。首选的修复方法是改进服务器配置,以启用更强大、更现代的加密套件和协议。理想情况下,应启用 TLSv1.2 和 AES-GCM,并启用并首选前向保密加密套件 (ECDHE)。

另一种方法是修改应用以使用自定义 SSLSocketFactory 与服务器通信。该工厂的设计目的是创建除了默认加密套件外,还启用了服务器所需的部分加密套件的 SSLSocket 实例。

注意:这些变更不适用于 WebView。

针对 Android 7.0 开发的应用

这些行为变更仅适用于针对 Android 7.0(API 24 级别)或更高版本开发的应用。针对 Android 7.0 编译或将 targetSdkVersion 设置为 Android 7.0 或更高版本的应用,必须修改其应用以正确支持这些行为(如果适用于应用)。

序列化变更

Android 7.0(API 24 级别)修复了默认 serialVersionUID 计算中的一个错误,该错误导致其与规范不匹配。

实现 Serializable 且未指定显式 serialVersionUID 字段的类可能会看到其默认 serialVersionUID 发生变化,这会导致在尝试反序列化在早期版本上序列化的类实例或由针对早期版本开发的应用序列化的类实例时抛出异常。错误消息将如下所示:

local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 4567

修复这些问题需要在任何受影响的类中添加一个 serialVersionUID 字段,其值取自错误消息中的 stream classdesc serialVersionUID,例如本例中的 1234。此更改符合编写序列化代码的所有良好实践建议,并将适用于所有版本的 Android。

修复的具体错误与静态初始化器方法(即 )的存在有关。根据规范,类中是否存在静态初始化器方法会影响为该类计算的默认 serialVersionUID。在错误修复之前,如果类没有静态初始化器,计算过程还会检查超类是否存在静态初始化器。

需要澄清的是,此变更不会影响针对 API 23 或更低级别开发的应用、具有 serialVersionUID 字段的类或具有静态初始化器方法的类。

其他重要点

当应用在 Android 7.0 上运行但针对较低的 API 级别开发时,如果用户更改显示尺寸,应用进程会被终止。应用必须能够优雅地处理这种情况。否则,当用户从“最近使用”恢复应用时,它会崩溃。您应该测试您的应用以确保不会发生此行为。您可以通过通过 DDMS 手动终止应用时触发相同的崩溃来验证。

针对 Android 7.0(API 24 级别)及以上版本开发的应用不会在密度变更时自动终止;但是,它们可能仍会对配置变更反应不佳。

Android 7.0 上的应用应能够优雅地处理配置变更,且不应在后续启动时崩溃。您可以通过更改字体大小(设置 > 显示 > 字体大小),然后从“最近使用”恢复应用来验证应用行为。

由于之前 Android 版本中的一个错误,系统未将主线程上的 TCP 套接字写入标记为严苛模式 (strict-mode) 违规。Android 7.0 修复了此错误。表现此行为的应用现在会抛出 android.os.NetworkOnMainThreadException。通常,在主线程上执行网络操作不是一个好主意,因为这些操作通常具有导致 ANR 和卡顿的高延迟。

Debug.startMethodTracing() 系列方法现在默认将输出存储在共享存储上的软件包特定目录中,而不是 SD 卡的顶层。这意味着应用不再需要请求 WRITE_EXTERNAL_STORAGE 权限即可使用这些 API。

许多平台 API 现在已开始检查通过 Binder 事务发送的大型负载,系统现在会将 TransactionTooLargeExceptions 重新抛出为 RuntimeExceptions,而不是静默记录或抑制它们。一个常见的例子是在 Activity.onSaveInstanceState() 中存储过多数据,当您的应用针对 Android 7.0 开发时,这会导致 ActivityThread.StopInfo 抛出 RuntimeException。

如果应用将 Runnable 任务发布到 View,且该 View 未附加到窗口,系统会将 Runnable 任务与该 View 一起排队;Runnable 任务直到 View 附加到窗口后才会执行。此行为修复了以下错误:

如果应用从非预期窗口 UI 线程的其他线程发布到 View,Runnable 可能会因此在错误的线程上运行。

如果 Runnable 任务是从非 Looper 线程的其他线程发布的,应用可能会暴露该 Runnable 任务。

如果在 Android 7.0 上具有 DELETE_PACKAGES 权限的应用尝试删除一个包,但该包是由其他应用安装的,则系统需要用户确认。在这种情况下,当应用调用 PackageInstaller.uninstall() 时,应用应预期返回状态为 STATUS_PENDING_USER_ACTION。

名为 Crypto 的 JCA 提供程序已被弃用,因为其唯一的算法 SHA1PRNG 在密码学上很弱。应用不能再使用 SHA1PRNG 来(不安全地)派生密钥,因为此提供程序不再可用。有关更多信息,请参阅博客文章 Android N 中弃用安全“Crypto”提供程序。

相关推荐

月光足逾期会怎么样?

月光足逾期会怎么样?

12-19 👁️ 7660
DNF耀之荣光手镯要做多久
孕晚期可以爬楼梯助产吗?还有哪些运动可以助产?