行为变更:以 Android 15 或更高版本为目标平台的应用

与以前的版本一样,Android 15 包含一些行为变更,这些变更可能会影响您的应用。以下行为变更仅影响以 Android 15 或更高版本为目标平台的应用。如果您的应用以 Android 15 或更高版本为目标平台,您应该修改自己的应用以适当地支持这些行为(如果适用)。

此外,请务必查看对 Android 15 上运行的所有应用都有影响的行为变更列表(无论应用的 targetSdkVersion 如何)。

核心功能

Android 15 修改或扩展了 Android 系统的各种核心功能。

前台服务变更

我们将对 Android 15 中的前台服务做出以下更改。

数据同步前台服务超时行为

Android 15 introduces a new timeout behavior to dataSync for apps targeting Android 15 or higher. This behavior also applies to the new mediaProcessing foreground service type.

The system permits an app's dataSync services to run for a total of 6 hours in a 24-hour period, after which the system calls the running service's Service.onTimeout(int, int) method (introduced in Android 15). At this time, the service has a few seconds to call Service.stopSelf(). When Service.onTimeout() is called, the service is no longer considered a foreground service. If the service does not call Service.stopSelf(), a failure will occur with this error message: "A foreground service of <fgs_type> did not stop within its timeout: <component_name>". In Beta 2, the failure message is shown as an ANR, but in a future Beta release, this failure message will throw a custom exception.

To avoid problems with this behavior change, you can do one or more of the following:

  1. Have your service implement the new Service.onTimeout(int, int) method. When your app receives the callback, make sure to call stopSelf() within a few seconds. (If you don't stop the app right away, the system generates a failure.)
  2. Make sure your app's dataSync services don't run for more than a total of 6 hours in any 24-hour period (unless the user interacts with the app, resetting the timer).
  3. Only start dataSync foreground services as a result of direct user interaction; since your app is in the foreground when the service starts, your service has the full six hours after the app goes to the background.
  4. Instead of using a dataSync foreground service, use an alternative API.

If your app's dataSync foreground services have run for 6 hours in the last 24, you cannot start another dataSync foreground service unless the user has brought your app to the foreground (which resets the timer). If you try to start another dataSync foreground service, the system throws ForegroundServiceStartNotAllowedException with an error message like "Time limit already exhausted for foreground service type dataSync".

新增媒体处理前台服务类型

Android 15 引入了一种新的前台服务类型 mediaProcessing。此服务类型适用于对媒体文件进行转码等操作。例如,媒体应用可能下载音频文件,并且需要将其转换为其他格式才能播放。您可以使用 mediaProcessing 前台服务来确保即使应用在后台运行,转换也会继续进行。

系统允许应用的 mediaProcessing 服务在 24 小时内总共运行 6 小时,之后系统会调用正在运行的服务的 Service.onTimeout(int, int) 方法(在 Android 15 中引入)。此时,服务只有几秒钟的时间来调用 Service.stopSelf()。如果服务未调用 Service.stopSelf(),则会发生故障,并显示以下错误消息:“A for the preground service of <fgs_type> was not stop in their timeout: <component_name>”。在 Beta 版 2 中,失败消息显示为 ANR,但在未来的 Beta 版中,此失败消息将抛出自定义异常。

为避免 ANR,您可以执行以下任一操作:

  1. 让您的服务实现新的 Service.onTimeout(int, int) 方法。当应用收到回调时,请确保在几秒钟内调用 stopSelf()。(如果您没有立即停止应用,系统会生成故障。)
  2. 确保应用的 mediaProcessing 服务在任何 24 小时内的运行时间总计不超过 6 小时(除非用户与应用互动,请重置计时器)。
  3. 仅因直接用户互动而启动 mediaProcessing 前台服务;由于您的应用在服务启动时正处于前台,因此在它进入后台后,您的服务有整整六个小时的时间。
  4. 请使用替代 API(例如 WorkManager),而不要使用 mediaProcessing 前台服务。

如果应用的 mediaProcessing 前台服务在过去 24 天内已经运行了 6 小时,那么除非用户已将您的应用带到前台(这会重置计时器),否则您无法启动其他 mediaProcessing 前台服务。如果您尝试启动另一个 mediaProcessing 前台服务,系统会抛出 ForegroundServiceStartNotAllowedException,并显示“时间限制已用尽前台服务类型 mediaProcessing”这样的错误消息。

如需详细了解 mediaProcessing 服务类型,请参阅 Android 15 前台服务类型的变更:媒体处理

对启动前台服务的 BOOT_COMPLETED 广播接收器的限制

针对启动前台服务的 BOOT_COMPLETED 广播接收器存在新的限制。BOOT_COMPLETED 接收器不能启动以下类型的前台服务:

如果 BOOT_COMPLETED 接收器尝试启动任何此类前台服务,系统会抛出 ForegroundServiceStartNotAllowedException

限制在应用拥有 SYSTEM_ALERT_WINDOW 权限的情况下启动前台服务

Previously, if an app held the SYSTEM_ALERT_WINDOW permission, it could launch a foreground service even if the app was currently in the background (as discussed in exemptions from background start restrictions).

If an app targets Android 15, this exemption is now narrower. The app now needs to have the SYSTEM_ALERT_WINDOW permission and also have a visible overlay window. That is, the app needs to first launch a TYPE_APPLICATION_OVERLAY window and the window needs to be visible before you start a foreground service.

If your app attempts to start a foreground service from the background without meeting these new requirements (and it does not have some other exemption), the system throws ForegroundServiceStartNotAllowedException.

If your app declares the SYSTEM_ALERT_WINDOW permission and launches foreground services from the background, it may be affected by this change. If your app gets a ForegroundServiceStartNotAllowedException, check your app's order of operations and make sure your app already has an active overlay window before it attempts to start a foreground service from the background. You can check if your overlay window is currently visible by calling View.getWindowVisibility(), or you can override View.onWindowVisibilityChanged() to get notified whenever the visibility changes.

对应用何时可以修改“勿扰”模式的全局状态所做的更改

Apps that target Android 15 can no longer change the global state or policy of Do Not Disturb (DND) on a device (either by modifying user settings, or turning off DND mode). Instead, apps must contribute an AutomaticZenRule, which the system combines into a global policy with the existing most-restrictive-policy-wins scheme. Calls to existing APIs that previously affected global state (setInterruptionFilter, setNotificationPolicy) result in the creation or update of an implicit AutomaticZenRule, which is toggled on and off depending on the call-cycle of those API calls.

Note that this change only affects observable behavior if the app is calling setInterruptionFilter(INTERRUPTION_FILTER_ALL) and expects that call to deactivate an AutomaticZenRule that was previously activated by their owners.

OpenJDK 17 变更

Android 15 continues the work of refreshing Android's core libraries to align with the features in the latest OpenJDK LTS releases.

One of these changes can affect app compatibility for apps targeting Android 15:

  • Changes to string formatting APIs: Validation of argument index, flags, width, and precision are now more strict when using the following String.format() and Formatter.format() APIs:

    For example, the following exception is thrown when an argument index of 0 is used (%0 in the format string):

    IllegalFormatArgumentIndexException: Illegal format argument index = 0
    

    In this case, the issue can be fixed by using an argument index of 1 (%1 in the format string).

  • Changes to component type of Arrays.asList(...).toArray(): When using Arrays.asList(...).toArray(), the component type of the resulting array is now an Object—not the type of the underlying array's elements. So the following code throws a ClassCastException:

    String[] elements = (String[]) Arrays.asList("one", "two").toArray();
    

    For this case, to preserve String as the component type in the resulting array, you could use Collection.toArray(Object[]) instead:

    String[] elements = Arrays.asList("two", "one").toArray(new String[0]);
    
  • Changes to language code handling: When using the Locale API, language codes for Hebrew, Yiddish, and Indonesian are no longer converted to their obsolete forms (Hebrew: iw, Yiddish: ji, and Indonesian: in). When specifying the language code for one of these locales, use the codes from ISO 639-1 instead (Hebrew: he, Yiddish: yi, and Indonesian: id).

  • Changes to random int sequences: Following the changes made in https://bugs.openjdk.org/browse/JDK-8301574, the following Random.ints() methods now return a different sequence of numbers than the Random.nextInt() methods do:

    Generally, this change shouldn't result in app-breaking behavior, but your code shouldn't expect the sequence generated from Random.ints() methods to match Random.nextInt().

安全性

Android 15 包含一些旨在提升系统安全性的变更,有助于保护应用和用户免受恶意应用的侵害。

安全的后台 activity 启动

Android 15 通过添加变更来防止恶意后台应用将其他应用带到前台、提升其权限和滥用用户互动,从而保护用户免受恶意应用的侵害,并使用户能够更好地控制其设备。从 Android 10(API 级别 29)开始,后台 activity 启动已受到限制。

阻止与堆栈中顶部 UID 不匹配的应用启动 activity

恶意应用可以在同一任务内启动另一个应用的 activity,然后叠加在上面,制造出作为该应用的错觉。这种“任务劫持”攻击绕过了当前的后台启动限制,因为它都发生在同一可见任务内。为了降低这种风险,Android 15 添加了一个标志,用于阻止与堆栈上的顶级 UID 不匹配的应用启动 activity。如需选择启用应用的所有 activity,请更新应用的 AndroidManifest.xml 文件中的 allowCrossUidActivitySwitchFromBelow 属性:

<application android:allowCrossUidActivitySwitchFromBelow="false" >

如果满足以下所有条件,则新的安全措施就会生效:

  • 执行启动的应用以 Android 15 为目标平台。
  • 任务堆栈顶部的应用以 Android 15 为目标平台。
  • 所有可见的活动均已启用新版保护措施

如果启用了安全措施,应用如果完成自己的任务,可能会返回主屏幕,而不是返回上一个可见的应用。

其他变更

除了 UID 匹配的限制之外,还包含以下其他更改:

  • PendingIntent 创建者更改为默认阻止后台 activity 启动。这有助于防止应用意外创建可能会被恶意操作者滥用的 PendingIntent
  • 请勿将应用置于前台,除非 PendingIntent 发送者允许该应用。此变更旨在防止恶意应用滥用在后台启动 activity 的功能。默认情况下,不允许应用将任务堆栈置于前台,除非创建者允许后台 activity 启动特权或发送者具有后台 activity 启动特权。
  • 控制任务堆栈的顶层 activity 完成其任务的方式。如果顶层 activity 完成某项任务,Android 会返回到上次处于活动状态的任务。此外,如果非顶层 activity 完成其任务,Android 将返回主屏幕;它不会阻止此非顶部 activity 的完成。
  • 防止将其他应用中的任意 activity 启动到您自己的任务中。这项变更会通过创建看似来自其他应用的活动,防止恶意应用向用户进行钓鱼式攻击。
  • 阻止系统考虑将不可见窗口用于后台 activity 启动。这有助于防止恶意应用滥用后台 activity 启动来向用户显示不受欢迎或恶意内容。

更安全的 intent

Android 15 introduces new security measures to make intents safer and more robust. These changes are aimed at preventing potential vulnerabilities and misuse of intents that can be exploited by malicious apps. There are two main improvements to the security of intents in Android 15:

  • Match target intent-filters: Intents that target specific components must accurately match the target's intent-filter specifications. If you send an intent to launch another app's activity, the target intent component needs to align with the receiving activity's declared intent-filters.
  • Intents must have actions: Intents without an action will no longer match any intent-filters. This means that intents used to start activities or services must have a clearly defined action.

Kotlin


fun onCreate() {
    StrictMode.setVmPolicy(VmPolicy.Builder()
        .detectUnsafeIntentLaunch()
        .build()
    )
}

Java


public void onCreate() {
    StrictMode.setVmPolicy(new VmPolicy.Builder()
            .detectUnsafeIntentLaunch()
            .build());
}

用户体验和系统界面

Android 15 包含一些变更,旨在打造更一致、更直观的用户体验。

窗口边衬区更改

Android 15 中有两个与窗口边衬区相关的变更:默认强制执行无边框模式;还存在配置变更,例如系统栏的默认配置。

全面强制执行

Apps are edge-to-edge by default on devices running Android 15 if the app is targeting Android 15.

An app that targets Android 14 and is not edge-to-edge on an Android 15 device.


An app that targets Android 15 and is edge-to-edge on an Android 15 device. This app mostly uses Material 3 Compose Components that automatically apply insets. This screen is not negatively impacted by the Android 15 edge-to-edge enforcement.

This is a breaking change that might negatively impact your app's UI. The changes affect the following UI areas:

  • Gesture handle navigation bar
    • Transparent by default.
    • Bottom offset is disabled so content draws behind the system navigation bar unless insets are applied.
    • setNavigationBarColor and R.attr#navigationBarColor are deprecated and don't affect gesture navigation.
    • setNavigationBarContrastEnforced and R.attr#navigationBarContrastEnforced continue to have no effect on gesture navigation.
  • 3-button navigation
    • Opacity set to 80% by default, with color possibly matching the window background.
    • Bottom offset disabled so content draws behind the system navigation bar unless insets are applied.
    • setNavigationBarColor and R.attr#navigationBarColor are set to match the window background by default. The window background must be a color drawable for this default to apply. This API is deprecated but continues to affect 3-button navigation.
    • setNavigationBarContrastEnforced and R.attr#navigationBarContrastEnforced is true by default, which adds an 80% opaque background across 3-button navigation.
  • Status bar
    • Transparent by default.
    • The top offset is disabled so content draws behind the status bar unless insets are applied.
    • setStatusBarColor and R.attr#statusBarColor are deprecated and have no effect on Android 15.
    • setStatusBarContrastEnforced and R.attr#statusBarContrastEnforced are deprecated but still have an effect on Android 15.
  • Display cutout
    • layoutInDisplayCutoutMode of non-floating windows must be LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS. SHORT_EDGES, NEVER and DEFAULT are interpreted as ALWAYS so that users don't see a Black bar caused by the display cutout and appear edge-to-edge.

The following example shows an app before and after targeting Android 15, and before and after applying insets.

An app that targets Android 14 and is not edge-to-edge on an Android 15 device.
An app that targets Android 15 and is edge-to-edge on an Android 15 device. However, many elements are now occluded by the status bar, 3-button navigation bar, or display cutout due to the Android 15 edge-to-edge enforcements. Occluded UI includes the Material 2 top app bar, floating action buttons, and list items.
An app that targets Android 15, is edge to edge on an Android 15 device and applies insets so that UI is not occluded.
What to check if your app is already edge-to-edge

If your app is already edge-to-edge and applies insets, you are mostly unimpacted, except in the following scenarios. However, even if you think you aren't impacted, we recommend you test your app.

  • You have a non-floating window, such as an Activity that uses SHORT_EDGES, NEVER or DEFAULT instead of LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS. If your app crashes on launch, this might be due to your splashscreen. You can either upgrade the core splashscreen dependency to 1.2.0-alpha01 or later or set window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutInDisplayCutoutMode.always.
  • There might be lower-traffic screens with occluded UI. Verify these less-visited screens don't have occluded UI. Lower-traffic screens include:
    • Onboarding or sign-in screens
    • Settings pages
What to check if your app is not already edge-to-edge

If your app is not already edge-to-edge, you are most likely impacted. In addition to the scenarios for apps that are already edge-to-edge, you should consider the following:

  • If your app uses Material 3 Components ( androidx.compose.material3) in compose, such as TopAppBar, BottomAppBar, and NavigationBar, these components are likely not impacted because they automatically handle insets.
  • If your app is using Material 2 Components ( androidx.compose.material) in Compose, these components don't automatically handle insets. However, you can get access to the insets and apply them manually. In androidx.compose.material 1.6.0 and later, use the windowInsets parameter to apply the insets manually for BottomAppBar, TopAppBar, BottomNavigation, and NavigationRail. Likewise, use the contentWindowInsets parameter for Scaffold.
  • If your app uses views and Material Components (com.google.android.material), most views-based Material Components such as BottomNavigationView, BottomAppBar, NavigationRailView, or NavigationView, handle insets and require no additional work. However, you need to add android:fitsSystemWindows="true" if using AppBarLayout.
  • For custom composables, apply the insets manually as padding. If your content is within a Scaffold, you can consume insets using the Scaffold padding values. Otherwise, apply padding using one of the WindowInsets.
  • If your app is using views and BottomSheet, SideSheet or custom containers, apply padding using ViewCompat.setOnApplyWindowInsetsListener. For RecyclerView, apply padding using this listener and also add clipToPadding="false".
What to check if your app must offer custom background protection

If your app must offer custom background protection to 3-button navigation or the status bar, you app should place a composable or view behind the system bar using WindowInsets.Type#tappableElement() to get the 3-button navigation bar height or WindowInsets.Type#statusBars.

Additional edge-to-edge resources

See the Edge to Edge Views and Edge to Edge Compose guides for additional considerations on applying insets.

Deprecated APIs

The following APIs are now deprecated:

稳定配置

如果您的应用以 Android 15 或更高版本为目标平台,Configuration 将不再排除系统栏。如果您在 Configuration 类中使用屏幕尺寸计算布局,则应根据需要将其替换为合适的 ViewGroupWindowInsetsWindowMetricsCalculator 等更好的替代方案。

从 API 1 开始,Configuration 一直可用。它通常从 Activity.onConfigurationChanged 获取。它可提供窗口密度、方向和大小等信息。从 Configuration 返回的窗口大小的一个重要特征是它之前排除了系统栏。

配置大小通常用于资源选择(如 /res/layout-h500dp),这仍然是一个有效的用例。不过,我们一直不建议将其用于布局计算。如果这样做,您应该立即离开。您应该根据自己的使用场景,将 Configuration 替换为更合适的代码。

如果您使用其计算布局,请使用适当的 ViewGroup,例如 CoordinatorLayoutConstraintLayout。如果您使用它来确定系统导航栏的高度,请使用 WindowInsets。如果您想知道应用窗口的当前大小,请使用 computeCurrentWindowMetrics

以下列表介绍了受此更改影响的字段:

decker TextHeight 属性默认设为 true

对于以 Android 15 为目标平台的应用,elegantTextHeight TextView 属性默认变为 true,将默认使用的紧凑字体替换为一些具有较大垂直指标的脚本,并且这种字体更易于阅读。紧凑字体的引入是为了防止破坏布局;Android 13(API 级别 33)允许文本布局利用 fallbackLineSpacing 属性拉伸垂直高度,以防止许多此类破坏。

在 Android 15 中,紧凑字体仍保留在系统中,因此您的应用可以将 elegantTextHeight 设置为 false,以获得与之前相同的行为,但即将在未来版本中提供支持。因此,如果您的应用支持以下文字:阿拉伯语、老挝语、缅甸、泰米尔语、古吉拉特语、卡纳达语、马拉雅拉姆语、奥里亚语、泰卢固语或泰语,请将 elegantTextHeight 设置为 true,以测试应用。

以 Android 14(API 级别 34)及更低版本为目标平台的应用的 elegantTextHeight 行为。
以 Android 15 为目标平台的应用的 elegantTextHeight 行为。

复杂字母形状的 TextView 宽度变化

在以前的 Android 版本中,一些采用复杂形状的草体字体或语言可能会在上一个或下一个字符区域中绘制字母。在某些情况下,此类字母会在开始或结束位置被截断。从 Android 15 开始,TextView 会分配宽度来为此类字母绘制足够的空间,并允许应用请求左侧添加额外的内边距以防止被裁剪。

由于此变更会影响 TextView 确定宽度的方式,因此如果应用以 Android 15 或更高版本为目标平台,TextView 会默认分配更多宽度。您可以通过对 TextView 调用 setUseBoundsForWidth API 来启用或停用此行为。

由于添加左侧内边距可能会导致现有布局未对齐,因此默认情况下,系统不会添加内边距,即使对于以 Android 15 或更高版本为目标平台的应用也是如此。不过,您可以通过调用 setShiftDrawingOffsetForStartOverhang 添加额外的内边距以防止裁剪。

以下示例展示了这些更改如何改进某些字体和语言的文本布局。

以手写体字体显示的英语文本的标准布局。部分字母会被截断。相应的 XML 如下:

<TextView
    android:fontFamily="cursive"
    android:text="java" />
包含额外宽度和内边距的相同英文文本的布局。相应的 XML 如下:

<TextView
    android:fontFamily="cursive"
    android:text="java"
    android:useBoundsForWidth="true"
    android:shiftDrawingOffsetForStartOverhang="true" />
泰语文字的标准版式。部分字母被截断。 相应的 XML 如下:

<TextView
    android:text="คอมพิวเตอร์" />
相同泰语文本的布局,但有额外的宽度和内边距。相应的 XML 如下:

<TextView
    android:text="คอมพิวเตอร์"
    android:useBoundsForWidth="true"
    android:shiftDrawingOffsetForStartOverhang="true" />

EditText 的语言区域感知默认行高

在以前的 Android 版本中,文本布局拉伸了文本的高度,使其适应与当前语言区域匹配的字体的行高。例如,如果内容是日语,由于日语字体的行高比拉丁字体的行高略大,因此文本的高度就略大了。不过,尽管行高存在这些差异,但无论使用何种语言区域,EditText 元素的大小都是一致的,如下图所示:

三个表示 EditText 元素的框,这些框可以包含英语 (en)、日语 (ja) 和缅甸语 (my) 的文本。EditText 的高度相同,即使这两种语言的行高不同。

对于以 Android 15 为目标平台的应用,系统现在会为 EditText 预留最小行高,以匹配指定语言区域的参考字体,如下图所示:

三个表示 EditText 元素的框,这些框可以包含英语 (en)、日语 (ja) 和缅甸语 (my) 的文本。EditText 的高度现在包含空间,可适应这些语言字体的默认行高。

如果需要,您的应用可以通过将 useLocalePreferredLineHeightForMinimum 属性设置为 false 来恢复之前的行为,并且可以通过 Kotlin 和 Java 中的 setMinimumFontMetrics API 设置自定义最小行业指标。

摄像头和媒体

对于以 Android 15 或更高版本为目标平台的应用,Android 15 对相机和媒体行为做出了以下变更。

与请求音频焦点相关的限制

Apps that target Android 15 must be the top app or running an audio-related foreground service in order to request audio focus. If an app attempts to request focus when it does not meet one of these requirements, the call returns AUDIOFOCUS_REQUEST_FAILED.

A foreground service is considered audio-related if its type is mediaPlayback, camera, microphone, or phoneCall.

You can learn more about audio focus at Manage audio focus.

更新后的非 SDK 限制

Android 15 includes updated lists of restricted non-SDK interfaces based on collaboration with Android developers and the latest internal testing. Whenever possible, we make sure that public alternatives are available before we restrict non-SDK interfaces.

If your app does not target Android 15, some of these changes might not immediately affect you. However, while it's possible for your app to access some non-SDK interfaces depending on your app's target API level, using any non-SDK method or field always carries a high risk of breaking your app.

If you are unsure if your app uses non-SDK interfaces, you can test your app to find out. If your app relies on non-SDK interfaces, you should begin planning a migration to SDK alternatives. Nevertheless, we understand that some apps have valid use cases for using non-SDK interfaces. If you can't find an alternative to using a non-SDK interface for a feature in your app, you should request a new public API.

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