Android 14 又来了?别扶!抬起我来吧!

大家好,好久不见,从去年底写完年终总结之后就再也没有更新过文章,之前最多也就间隔一两个月时间,但这回间隔时间确实有点长,基本快半年了!也没有别的原因,就是工作太忙了。。。好了,废话不多说,开始今天的主题吧!

三年前写了一篇文章:安卓11来了,快!扶我起来,然后 Android 12 由于工作原因没有时间写,之后去年又写了一篇文章:安卓13来了,快!扶起我来!时间真的过的太快了,转眼间 Android 14 又来了,还是简单来看看咱们安卓开发者需要注意些什么吧!

如何获取 Android 14?

image.png

官方提供有三种方式:

这三种都挺好理解:如果你有 Pixel 的话(比较新的设备,老的不支持,Pixel 4a (5G) 之后的可以),直接申请就可以;如果咱们没有 Pixel 的话(大部分都没有),可以使用模拟器;最后一种是可以往设备中刷入 GSI,这个操作起来就比较复杂了,大家如果想搞一搞的话可以进官方文档看下:通用系统映像 (GSI)

所有应用需注意

一般来说 Android 版本更新都会有两种行为变更:

1、所有应用都会受到的影响,即使你的应用没有将 TargetSDK 升级到 34(即 Android 14),但你的应用可能在 Android 14 的设备中运行,那么就需要你做的适配

2、将 TargetSDK 升级到 34,应用也在 Andorid 14 的设备中运行时需要你做的适配

这一个模块咱们先来看第一个,即所有应用都需要做的适配。

默认拒绝设定精确的闹钟

精确的闹钟适用于用户指定的通知,或是在确切时间需要执行的操作。从 Android 14 开始,系统不再向以 Android 13 及更高版本为目标平台的大多数新安装应用预先授予 SCHEDULE_EXACT_ALARM 权限,该权限默认处于拒绝状态。

精确闹钟适用于用户指定的通知,或是在确切时间需要执行的操作。

SCHEDULE_EXACT_ALARM 是 Android 12 中引入的可让应用安排精确闹钟的权限,不再预先授予以 Android 13 和更高版本为目标平台的最新安装应用(默认情况下,设置为“拒绝”)。如果用户通过备份和恢复操作将应用数据转移到搭载 Android 14 的设备,则该权限仍然会被拒绝。如果现有应用已拥有此权限,则当设备升级到 Android 14 时,系统会预先授予此权限。

需要 SCHEDULE_EXACT_ALARM 权限才能通过以下 API 启动精确闹钟,否则系统会抛出 SecurityException

注意: 如果使用 OnAlarmListener 对象设置精确闹钟(例如在 setExact API 中),则不需要 SCHEDULE_EXACT_ALARM 权限。

SCHEDULE_EXACT_ALARM 权限的现有最佳实践仍然适用,其中包括:

日历或闹钟应用需要在应用停止运行时发送日历提醒、唤醒闹钟或提醒。这些应用可以请求 USE_EXACT_ALARM 常规权限。系统将在安装时授予 USE_EXACT_ALARM 权限,拥有此权限的应用将能够像具有 SCHEDULE_EXACT_ALARM 权限的应用一样安排精确闹钟。

当然,如果你的应用是系统应用,可以进行申请豁免。

当应用进入缓存时,上下文注册的广播将加入队列

在 Android 14 中,当应用处于缓存状态时,系统可能会将上下文注册的广播放入队列中。这与 Android 12(API 级别 31)为异步 binder 事务引入的队列行为类似。在清单中声明的广播不会加入队列,并且应用会从缓存状态中移除以进行广播传递。

当应用离开缓存状态(例如返回前台)时,系统会传递所有已加入队列的广播。某些广播的多个实例可以合并为一个广播。

应用只能终止自己的后台进程

从 Android 14 开始,当应用调用 killBackgroundProcesses() 时,该 API 只能终止自己应用的后台进程。

如果传入另一个应用的软件包名称的话,此方法对该应用的后台进程将没有任何影响,只会在 Logcat 中会显示以下消息:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Invalid packageName: com.example.anotherapp </pre>

应用不应使用 killBackgroundProcesses() API,也不得以其他方式尝试影响其他应用的进程生命周期,即使在旧版操作系统上也是如此。Android 旨在让缓存应用在后台运行,并在系统需要内存时自动终止它们。

最低可安装的目标 API 级别

从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于提高用户的安全性和隐私性。

恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私保护机制。例如,有些恶意软件应用使用 targetSdkVersion 22,以避免受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模型的约束。这项 Android 14 变更使恶意软件更难以规避安全和隐私权方面的改进限制。尝试安装以较低 API 级别为目标平台的应用将导致安装失败,并且 Logcat 中会显示以下消息:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7 </pre>

在升级到 Android 14 的设备上,targetSdkVersion 低于 23 的所有应用都将继续保持安装状态。如果需要测试以旧版 API 级别为目标平台的应用,请使用以下 ADB 命令:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">adb install --bypass-low-target-sdk-block FILENAME.apk </pre>

媒体包名称可能会隐藏

媒体库支持查询 OWNER_PACKAGE_NAME 列,该列表示存储特定媒体文件的应用。从 Android 14 开始,除非满足以下条件之一,否则系统会隐去此值:

  • 存储媒体文件的应用有一个软件包名称始终对其他应用可见。
  • 查询媒体库的应用会请求 QUERY_ALL_PACKAGES 权限。

关于不可关闭通知用户体验方式的变更

如果应用向用户显示不可关闭的前台通知的话需要注意:Android 14 中允许用户关闭此类通知。

这项变更适用于通过 Notification.Builder#setOngoing(true)NotificationCompat.Builder#setOngoing(true) 设置 Notification.FLAG_ONGOING_EVENT 来阻止用户关闭前台通知的应用。FLAG_ONGOING_EVENT 的行为已发生变化,使用户实际上能够关闭此类通知。

在以下情况下,此类通知仍不可关闭:

  • 当手机处于锁定状态时
  • 如果用户选择全部清除通知操作(有助于防止意外关闭)

此外,这一新行为不适用于以下用例中的不可关闭通知:

  • 使用 MediaStyle 创建的通知
  • 安全和隐私用例的政策限制使用
  • 企业设备政策控制器 (DPC) 和支持软件包

授予对照片和视频的部分访问权限

注意: 如果应用已经在使用照片选择器,则无需执行任何操作即可支持此变更。

在 Android 14 中,当应用请求 Android 13(API 级别 33)中引入的任何视觉媒体权限时,用户可以授予对其照片和视频的部分访问权限:READ_MEDIA_IMAGESREAD_MEDIA_VIDEO

新对话框会显示以下权限选项:

  • 选择照片和视频: Android 14 中的新功能。用户选择希望提供给应用的具体照片和视频。
  • 全部允许:用户授予对设备上的所有照片和视频的完整访问权限。
  • 不允许:用户拒绝授予所有访问权限。

如需在应用中更妥善地处理此更改,请考虑声明新的 READ_MEDIA_VISUAL_USER_SELECTED 权限。详细了解如何支持用户授予对其媒体库的部分访问权限

升级到 Android 14 需要做的适配

上面说了所有的应用在 Android 14 中需要做的适配,这里来看下将 TargetSDK 升级到 Android 14 的应用需要做的适配吧!

前台服务类型

如果 TargetSDK 是 Android 14,则必须为应用中的每个前台服务指定至少一个前台服务类型,系统期望具有特定类型的前台服务来满足特定的用例。下面列出了可供选择的前台服务类型:

下面来举一个栗子吧:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><manifest ...> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <application ...> <service android:name=".MyMediaPlaybackService" android:foregroundServiceType="mediaPlayback" android:exported="false"> </service> </application> </manifest> </pre>

注意:Android 14为运行状况和远程消息传递用例引入了前台服务类型。系统还为短期服务、特殊用例和系统豁免保留新类型。如果以 Android 14 为目标平台的应用未在清单中定义给定服务的类型,系统会在调用 startForeground() 时引发 MissingForegroundServiceTypeException

如果应用中的用例与这些类型中的任何一个都没有关联,官方建议咱们使用 WorkManager 或用户发起的数据传输作业。

WorkManager 大家都使用过,但“用户发起的数据传输作业”又是个什么东西呢?

这是 Android 14 中 Google 引入的一个新 API,用于指定作业必须是用户发起的数据传输作业。此 API 适用于需要由用户发起的持续时间较长的数据传输,例如从远程服务器下载文件。这些类型的任务应使用由用户发起的数据传输作业。

注意: 如果目前针对网络数据传输使用情形使用的是 WorkManager,官方建议还是继续使用该库,而不是使用由用户发起的数据传输作业。

由用户发起的数据传输作业需要具备以下新权限才能运行:RUN_USER_INITIATED_JOBS。系统会自动授予此权限(无需动态申请)。如果未在应用清单中声明此权限,系统会抛出 SecurityException

运行用户发起的数据传输作业的流程

如需运行用户发起的作业,请执行以下操作:

  1. 在清单中声明 RUN_USER_INITIATED_JOBS 权限:

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);"><manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest> </pre>

  2. 构建 JobInfo 对象时,调用新的 setUserInitiated()setDataTransfer() 方法。还可以在创建作业时通过调用 setEstimatedNetworkBytes() 提供载荷大小估算值:

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">`val networkRequestBuilder = NetworkRequest.Builder()
    .addCapability(NET_CAPABILITY_INTERNET)
    .addCapability(NET_CAPABILITY_VALIDATED)

    val jobInfo = JobInfo.Builder()
    // ...
    .setUserInitiated(true)
    .setDataTransfer(true)
    .setRequiredNetwork(networkRequestBuilder.build())
    .setEstimatedNetworkBytes(1024 * 1024 * 1024)
    // ...
    .build()` </pre>

  3. 在应用可见时或在允许的条件列表中列出的条件下调度作业:

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">val jobScheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(jobInfo) </pre>

  4. 执行作业时,请确保对 JobService 对象调用 setNotification()。该值用于在任务管理器和状态栏通知区域中告知用户作业正在运行:

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">`val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
    .setContentTitle("My user-initiated data transfer job")
    .setSmallIcon(android.R.mipmap.myicon)
    .setContentText("Job is running")
    .build()

    class CustomJobService : JobService() {
    override fun onStartJob(params: JobParameters?): Boolean {
    setNotification(params, notification.id, notification,
    JobService.JOB_END_NOTIFICATION_POLICY_DETACH)
    // Do the job execution.
    }
    }` </pre>

    注意: 如果未在短时间内调用 setNotification(),则会导致应用出现 ANR。

  5. 定期更新通知,让用户了解作业的状态和进度。如果在安排作业之前无法确定传输大小,请在知道传输大小之后使用新的 API updateEstimatedNetworkBytes() 更新传输大小。

  6. 执行完成后,调用 jobFinished() 以向系统表明作业已完成,或者应重新调度作业。

可以停止用户发起的数据传输作业

用户和系统都可以停止用户发起的传输作业。

由用户从任务管理器提供

用户可以停止显示在任务管理器中的用户发起的传输作业。

在用户按 Stop 时,系统会执行以下操作:

  • 立即终止应用的进程,包括正在运行的所有其他作业或前台服务。
  • 不针对任何正在运行的作业调用 onStopJob()
  • 阻止重新调度用户可见的作业。

因此,建议在发布的作业通知中提供控件,以便顺利停止和重新调度作业。

请注意,在特殊情况下,Stop 按钮不会显示在任务管理器中的作业旁边,或者该作业根本不会显示在任务管理器中。

由系统提供

与常规作业不同,用户发起的数据传输作业不受应用待机模式存储分区配额的影响。但是,如果出现以下任一情况,系统仍会停止作业:

  • 不再满足开发者定义的约束条件。
  • 系统确定该作业的运行时间超出了完成数据传输任务所需的时间。
  • 系统需要优先考虑系统运行状况,并因发热程度上升而停止作业。
  • 应用进程因设备内存不足而被终止。

系统停止作业(并非因为内存不足)时,系统会调用 onStopJob(),系统会在系统认为最佳的时间重试作业。检查应用是否可以保留数据传输状态(即使未调用 onStopJob()),并且应用可以在再次调用 onStartJob() 时恢复此状态。

允许安排用户发起的数据传输作业的条件

只有当应用处于可见窗口中或满足特定条件时,应用才能启动用户发起的数据传输作业。为确定何时可以安排用户发起的数据传输作业,系统会采用允许应用在特殊情况下从后台启动 activity 的一组相同条件。值得注意的是,这组条件与后台启动的前台服务限制的豁免集不同。

上述说明的例外情况包括:

  • 如果应用可以从后台启动 activity,则也可以从后台启动用户发起的数据传输作业。
  • 如果应用在最近用过屏幕上现有任务的返回堆栈中有 activity,单靠这一点并不允许运行用户发起的数据传输作业。

如果作业安排在允许的条件列表中未列出的其他时间,则作业将失败并返回 RESULT_FAILURE 错误代码。

适用于用户发起的数据传输作业的约束条件

为了支持在最佳时间点运行的作业,Android 提供了为每种作业类型分配约束条件的功能。这些约束条件从 Android 13 开始就已经可用。

注意:下表仅比较了因作业类型而异的约束条件。如需了解所有约束条件,请参阅 JobScheduler 开发者页面工作约束条件

下表显示了支持给定作业约束条件的不同作业类型,以及 WorkManager 支持的作业约束条件集。可以使用表格前的搜索栏按作业约束方法的名称过滤表格。

以下是用户发起的数据传输作业允许使用的约束条件:

  • setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)
  • setClipData()
  • setEstimatedNetworkBytes()
  • setMinimumNetworkChunkBytes()
  • setPersisted()
  • setNamespace()
  • setRequiredNetwork()
  • setRequiredNetworkType()
  • setRequiresBatteryNotLow()
  • setRequiresCharging()
  • setRequiresStorageNotLow()

OpenJDK 17 更新

Android 14 将继续更新 Android 的核心库,以与最新 OpenJDK LTS 版本中的功能保持一致,包括适合应用和平台开发者的库更新和 Java 17 语言支持。

以下变更可能会影响应用兼容性:

  • 对正则表达式的更改:现在,为了更严格地遵循 OpenJDK 的语义,不允许无效的组引用。大家可能会看到 java.util.regex.Matcher 类抛出 IllegalArgumentException 的新情况,因此请务必测试应用中使用正则表达式的情形。如需在测试期间启用或停用此变更,请使用兼容性框架工具切换 DISALLOW_INVALID_GROUP_REFERENCE 标志。
  • UUID 处理:现在,验证输入参数时,java.util.UUID.fromString() 方法会执行更严格的检查,因此可能会在反序列化期间看到 IllegalArgumentException。如需在测试期间启用或停用此变更,请使用兼容性框架工具切换 ENABLE_STRICT_VALIDATION 标志。
  • ProGuard 问题:有时,在尝试使用 ProGuard 缩减、混淆和优化应用时,添加 java.lang.ClassValue 类会导致问题。问题源自 Kotlin 库,该库会根据 Class.forName("java.lang.ClassValue") 是否会返回类更改运行时行为。如果应用是根据没有 java.lang.ClassValue 类的旧版运行时开发的,则这些优化可能会将 computeValue 方法从派生自 java.lang.ClassValue 的类中移除。

对隐式 intent 和待处理 intent 的限制

对于以 Android 14 为目标平台的应用,Android 会通过以下方式限制应用向内部应用组件发送隐式 intent:

  • 隐式 intent 只能传送到导出的组件。应用必须使用显式 intent 传送到未导出的组件,或将该组件标记为已导出。
  • 如果应用通过未指定组件或软件包的 intent 创建可变待处理 intent,系统现在会抛出异常。

这些变更可防止恶意应用拦截意在供应用内部组件使用的隐式 intent。

例如,下面是可以在应用的清单文件中声明的 intent 过滤器

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><activity android:name=".AppActivity" android:exported="false"> <intent-filter> <action android:name="com.example.action.APP_ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </pre>

如果应用尝试使用隐式 intent 启动此 activity,则系统会抛出异常:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// Throws an exception when targeting Android 14. context.startActivity(new Intent("com.example.action.APP_ACTION")); </pre>

如需启动非导出的 activity,应用应改用显式 intent:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// This makes the intent explicit. Intent explicitIntent = new Intent("com.example.action.APP_ACTION") explicitIntent.setPackage(context.getPackageName()); context.startActivity(explicitIntent); </pre>

在运行时注册的广播接收器必须指定导出行为

以 Android 14 为目标平台并使用上下文注册的接收器的应用和服务必须指定以下标志,以指明接收器是否应导出到设备上的所有其他应用:RECEIVER_EXPORTEDRECEIVER_NOT_EXPORTED。此要求有助于利用 Android 13 中引入的这些接收器的功能,来保护应用免受安全漏洞的影响。

仅接收系统广播的接收器的例外情况

如果应用仅通过 Context#registerReceiver 方法(例如 Context#registerReceiver())针对系统广播注册接收器,那么它在注册接收器时不应指定标志。

更安全的动态代码加载

如果应用以 Android 14 为目标平台并使用动态代码加载 (DCL) 功能,则必须将所有动态加载的文件标记为只读。否则,系统会抛出异常。官方建议应用尽可能避免动态加载代码,因为这样做会大大增加应用因代码注入或代码篡改而遭到入侵的风险。

如果必须动态加载代码,请使用以下方法,在动态文件(例如 DEX、JAR 或 APK 文件)打开并写入任何内容之前立即将其设为只读:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">File jar = new File("DYNAMICALLY_LOADED_FILE.jar"); try (FileOutputStream os = new FileOutputStream(jar)) { // Set the file to read-only first to prevent race conditions jar.setReadOnly(); // Then write the actual file content } catch (IOException e) { ... } PathClassLoader cl = new PathClassLoader(jar, parentClassLoader); </pre>

处理已存在的动态加载文件

为防止系统对现有动态加载的文件抛出异常,官方建议先删除并重新创建文件,然后再尝试在应用中重新动态加载这些文件。重新创建文件时,请按照上述指南在写入时将文件标记为只读。或者,可以将现有文件重新标记为只读,但在这种情况下,官方建议先验证文件的完整性(例如,对照可信值检查文件的签名)以保护应用免遭恶意操作的影响。

压缩路径遍历

对于以 Android 14 为目标平台的应用,Android 会通过以下方式防止 Zip 路径遍历漏洞:如果 Zip 文件条目名称包含“..”或以“/”开头,ZipFile(String)ZipInputStream.getNextEntry() 会抛出 ZipException

应用可以通过调用 dalvik.system.ZipPathValidator.clearCallback() 选择停用此验证。

针对从后台启动 activity 的其他限制

对于以 Android 14 为目标平台的应用,系统会进一步限制允许应用在后台启动 activity 的时间:

这些更改扩大了一组现有限制条件的范围,目的是防止恶意应用滥用 API 以在后台启动干扰性活动,从而保护用户。

更新后的非 SDK 限制

Android 14 包含更新后的受限非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。在限制使用非 SDK 接口之前,官方说会尽可能确保有可用的公开替代方案。

如果应用没有升级到 Android 14 ,其中一些变更可能不会立即产生影响。然而,虽然目前仍可以使用一些非 SDK 接口(具体取决于应用的目标 API 级别),但只要使用任何非 SDK 方法或字段,终归存在导致应用出问题的显著风险。

如果不确定自己的应用是否使用了非 SDK 接口,则可以测试应用来进行确认。如果应用依赖于非 SDK 接口,应该开始计划迁移到 SDK 替代方案。然而,官方知道某些应用具有使用非 SDK 接口的有效用例。如果无法为应用中的某项功能找到使用非 SDK 接口的替代方案,应请求新的公共 API

如需详细了解此 Android 版本中的变更,可以参阅 Android 14 中有关限制非 SDK 接口的更新。如需全面了解有关非 SDK 接口的详细信息,可以参阅对非 SDK 接口的限制

Android 14 的新功能

上面的一大堆就是在 Android 14 中应用需要做的适配,其实看着很多,实际上需要应用修改的地方并不是很多。Android 14 面向开发者还引入了一些出色的新功能和 API,一起来看下吧!

各应用语言偏好设定

Android 14 扩展了 Android 13(API 级别 33)中引入的按应用设定语言功能,并包含以下额外功能:

  • 自动生成应用的 localeConfig:从 Android Studio Giraffe Canary 7 和 AGP 8.1.0-alpha07 开始,可以将应用配置为自动支持各应用语言偏好设定。Android Gradle 插件会根据项目资源生成 LocaleConfig 文件,并在最终清单文件中添加对该文件的引用,这样就不再需要手动创建或更新该文件。AGP 使用应用模块的 res 文件夹中的资源以及任何库模块依赖项来确定要在 LocaleConfig 文件中添加的语言区域。如需了解详情并提供反馈,请参阅按应用自动设定语言支持
  • 动态更新应用的 localeConfig:使用 LocaleManager 方法中的 setOverrideLocaleConfig()getOverrideLocaleConfig() 可以在设备的系统设置中动态更新应用的受支持语言列表。有了这种灵活性,就可以按区域自定义支持的语言列表、运行 A/B 实验,或者如果应用通过服务器端推送进行本地化,则可以提供更新后的语言区域列表。
  • 输入法 (IME) 的应用语言可见性:IME 可以利用 getApplicationLocales() 方法查看当前应用的语言,并将 IME 语言与该语言进行匹配。

语法变化 API

有 30 亿人在使用区分性别的语言,此类语言的语法类别(例如名词、动词、形容词和介词)会根据交谈所涉及的人或物的性别而变化。传统上,许多区分性别的语言使用阳性语法性别作为默认或通用性别。

以错误的语法性别来称呼用户,例如以阳性语法性别来称呼女性,可能会对她们的表现和态度产生负面影响。相比之下,界面语言如果能正确反映用户的语法性别,就可以提高用户互动度,并提供更个性化、更自然的用户体验。

为帮助针对区分性别的语言构建以用户为中心的界面,Android 14 引入了语法变化 API,让大家无需重构应用便能添加对语法性别的支持。

地区偏好设置

区域偏好使用户能够修改温度单位(华氏度、摄氏度),一周的第一天(周日为第一天还是周一)。

新的Android设置菜单为用户提供了一个可发现和集中的位置来更改应用程序偏好。这些首选项在备份和恢复过程中也会持续存在。几个api和意图-如' getTemperatureUnit '' getFirstDayOfWeek ' -授予应用程序读取访问用户的偏好,所以你的应用程序可以调整它如何显示信息。大家还可以在' ACTION_LOCALE_CHANGED '上注册' BroadcastReceiver '来处理区域首选项更改时的区域配置更改。

要找到这些设置,请打开设置应用程序并导航到系统>语言和输入>区域偏好,官方截图如下:

image.png

路径现在可查询和插值

Android 的 Path API 是一种强大且灵活的机制,可用于创建和渲染矢量图形,能够描边或填充路径,根据线段、二次曲线或立方曲线构建路径,执行布尔运算以获取更复杂的形状,或同时执行所有这些操作。但是无法了解 Path 对象中实际包含的内容;该对象的内部信息在创建后对于调用方是不透明的。

如需创建 Path,可以调用 moveTo()lineTo()cubicTo() 等方法来添加路径片段。但无法询问该路径有哪些片段,因此必须在创建时保留该信息。

从 Android 14 开始,可以查询路径以了解其内部内容。首先,需要使用 Path.getPathIterator API 获取 PathIterator 对象:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Path path = new Path(); path.moveTo(1.0F, 1.0F); path.lineTo(2.0F, 2.0F); path.close(); PathIterator pathIterator = path.getPathIterator(); </pre>

接下来,可以调用 PathIterator 逐个遍历片段,并检索每个片段的所有必要数据。以下示例使用了 PathIterator.Segment 对象,它会打包数据:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">while (pathIterator.hasNext()) { PathIterator.Segment segment = pathIterator.next(); Log.i(LOG_TAG, "segment: " + segment.getVerb() + ", " + segment.getPoints()); } </pre>

PathIterator 还有一个非分配版 next(),可以在其中传入缓冲区来保存点数据。

查询 Path 数据的一个重要用例是插值。例如,大家可能想在两个不同的路径之间添加动画(或变形)。为了进一步简化该用例,Android 14 针对 Path 还有一个新的 interpolate() 方法。假设两个路径具有相同的内部结构,interpolate() 方法会使用该插值结果创建一个新的 Path。以下示例返回了一个形状介于 pathotherPath 之间的一半(线性插值为 0.5)的路径:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Path interpolatedResult = new Path(); if (path.isInterpolatable(otherPath)) { path.interpolate(otherPath, 0.5F, interpolatedResult); } </pre>

还有一点需要注意:Jetpack graphics-path 库现已发布 Alpha 版,它也为早期版本的 Android 提供了类似的 API。

检测用户何时截取设备屏幕截图

为了创建更标准化的截图检测体验,Android 14 中引入了一个保护隐私的截图检测API。这个API允许应用程序以每个活动为基础注册回调。当用户在活动可见时截取屏幕截图时,调用这些回调函数,并通知用户。

注意: 回调不提供实际截图的图像。当用户截图时,屏幕上显示的内容由应用进行决定。

支持的用例

在Android 14中,系统API仅在用户执行特定的硬件按键组合时检测截图。API不会检测在运行与屏幕截图相关的测试命令时所截取的屏幕截图,包括ADB或在捕获设备当前屏幕内容的仪器测试中截取的屏幕截图。

实现步骤

要添加截图检测,声明新的' DETECT_SCREEN_CAPTURE '安装时权限:

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" /> </pre>

然后,完成以下步骤,在你的应用程序中的每个活动,用户可能会捕获屏幕截图:

  1. 通过覆盖 onScreenCapture() 函数实现回调。在这个回调中,应用程序可以采取一些行动,比如警告另一个用户有人截取了消息对话的截图。

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">final Activity.ScreenCaptureCallback screenCaptureCallback = new Activity.ScreenCaptureCallback() { @Override public void onScreenCaptured() { // Add logic to take action in your app. } }; </pre>

  2. ActivityonStart() 方法中,注册截图回调。

    注意: 考虑到每个屏幕截图检测信号都会显示一个通知,开发者应该在用户开始使用屏幕截图检测api的活动时向用户提供上下文通知,这样系统通知就不会让用户感到意外。

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">@Override protected void onStart() { super.onStart(); // Pass in the callback created in the previous step // and the intended callback executor (e.g. Activity's mainExecutor). registerScreenCaptureCallback(executor, screenCaptureCallback); } </pre>

  3. ActivityonStop() 方法中,注销截图回调:

    <pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1em; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248);">@Override protected void onStop() { super.onStop(); unregisterScreenCaptureCallback(screenCaptureCallback); } </pre>

控制捕获屏幕截图能力

如果你不希望一个应用程序的活动的内容出现在屏幕截图,或在非安全显示,可以设置' FLAG_SECURE '显示标志。

<pre style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 16px; overflow: auto; position: relative; line-height: 1.75; color: rgb(51, 51, 51); background: rgb(248, 248, 248); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">activity.getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE); </pre>

Android 14 中的企业功能新变化

联系人

Android 14 添加了以下两个字段:

这两个字段一起可让具有 READ_CONTACTS 权限的个人应用列出所有工作资料联系人和电话号码,前提是 DevicePolicyManager 中的跨资料联系人政策允许这么做。

跨资料访问联系人

可以使用 DevicePolicyManager 中的以下方法设置和查询该政策,这些方法指定了允许哪些软件包从个人资料中访问工作联系人:

这些方法可向后兼容,应改用这些方法,而不是现在已废弃的下列方法:

跨资料来电显示搜索

同样,Android 14 还针对跨资料来电显示搜索添加了以下方法:

这些方法可向后兼容,应改用这些方法,而不是现在已废弃的下列方法:

超宽带

超宽带 (UWB) 是一种无线技术,在很大一部分射频频谱内,都能使用非常低的能量水平进行短距离、高带宽通信。

从 Android 14 开始,设备或资料所有者可以通过 DevicePolicyManager.addUserRestriction() 应用 DISALLOW_ULTRA_WIDEBAND_RADIO 用户限制,对组织所有的设备禁止使用 UWB。

废弃

Android 14 废弃了以下 API,值得引起注意:

总结

虽然目前 Android 14 还没有发布稳定版版,但现在已经是 Beta 版本了,之后 API 这些也不会进行大的变动的,剩下的应该就是性能上的一些优化了,大家可以放心进行查看。其实上面描述的都可以在官方文档中找到,我只是做了一些整理,方便大家进行查看配置。

OK,就到这里吧,如果内容对你有帮助,别忘了点个赞👍!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容