שינויים בהתנהגות: אפליקציות שמטרגטות את Android 15 ואילך

בדומה לגרסאות קודמות, Android 15 כולל שינויים בהתנהגות שעשויים להשפיע על האפליקציה שלכם. שינויי ההתנהגות הבאים רלוונטיים רק לאפליקציות שמטרגטות את Android 15 ואילך. אם האפליקציה שלכם מטרגטת את Android מגרסה 15 ואילך, אתם צריכים לשנות את האפליקציה כדי שהיא תתמוך בהתנהגויות האלה, במקרים הרלוונטיים.

חשוב גם לבדוק את רשימת השינויים בהתנהגות שמשפיעים על כל האפליקציות שפועלות ב-Android 15, בלי קשר ל-targetSdkVersion של האפליקציה.

פונקציונליות עיקרית

‫Android 15 משנה או מרחיב יכולות ליבה שונות של מערכת Android.

שינויים בשירותים שפועלים בחזית

We are making the following changes to foreground services with Android 15.

Data sync foreground service timeout behavior

ב-Android 15 נוספה התנהגות חדשה של זמן קצוב לתפוגה ל-dataSync באפליקציות שמטרגטות ל-Android 15 (רמת API 35) ואילך. ההתנהגות הזו רלוונטית גם לסוג החדש של שירות mediaProcessing שפועל בחזית.

המערכת מאפשרת לשירותי dataSync של אפליקציה לפעול במשך 6 שעות בסך הכול בתקופה של 24 שעות, ולאחר מכן המערכת קוראת ל-method‏ Service.onTimeout(int, int) של השירות שפועל (התכונה הזו נוספה ב-Android 15). בשלב הזה, לשירות יש כמה שניות לבצע קריאה ל-Service.stopSelf(). כשקוראים לפונקציה Service.onTimeout(), השירות כבר לא נחשב לשירות שפועל בחזית. אם השירות לא קורא ל-Service.stopSelf(), המערכת תיצור חריגה פנימית. החריגה מתועדת ב-Logcat עם ההודעה הבאה:

Fatal Exception: android.app.RemoteServiceException: "A foreground service of
type dataSync did not stop within its timeout: [component name]"

כדי למנוע בעיות שקשורות לשינוי הזה בהתנהגות, אפשר לבצע אחת או יותר מהפעולות הבאות:

  1. מטמיעים את השיטה החדשה Service.onTimeout(int, int) בשירות. כשהאפליקציה תקבל את הקריאה החוזרת, חשוב להתקשר למספר stopSelf() תוך כמה שניות. (אם לא עוצרים את האפליקציה מיד, המערכת יוצרת כשל).
  2. חשוב לוודא ששירותי dataSync של האפליקציה לא פועלים במשך יותר מ-6 שעות בסך הכול בכל תקופה של 24 שעות (אלא אם המשתמש יוצר אינטראקציה עם האפליקציה, ומאפס את הטיימר).
  3. הפעלת שירותים שפועלים בחזית dataSync רק כתוצאה מאינטראקציה ישירה של המשתמש. מכיוון שהאפליקציה פועלת בחזית כשהשירות מופעל, השירות פועל במשך 6 שעות בלבד אחרי שהאפליקציה עוברת לרקע.
  4. במקום להשתמש בשירות dataSync שפועל בחזית, צריך להשתמש בAPI חלופי.

אם השירותים שפועלים בחזית ב-dataSync באפליקציה פועלים במשך 6 שעות ב-24 השעות האחרונות, לא ניתן להפעיל שירות נוסף שפועל בחזית של dataSync אלא אם המשתמש העביר את האפליקציה לחזית האפליקציה (הפעולה הזו מאפסת את הטיימר). אם תנסו להפעיל שירות אחר שפועל בחזית של dataSync, המערכת תגרור ForegroundServiceStartNotAllowedException הודעת שגיאה כמו "Time limit limit for data Sync type" (סוג שירות שפועל בחזית).

בדיקה

כדי לבדוק את התנהגות האפליקציה, אפשר להפעיל זמן קצוב לסיום הסנכרון של הנתונים גם אם האפליקציה לא מטרגטת ל-Android 15 (כל עוד האפליקציה פועלת במכשיר עם Android 15). כדי להפעיל זמן קצוב לתפוגה, מריצים את הפקודה הבאה ב-adb:

adb shell am compat enable FGS_INTRODUCE_TIME_LIMITS your-package-name

אפשר גם לשנות את משך הזמן של הזמן הקצוב לתפוגה, כדי שיהיה קל יותר לבדוק איך האפליקציה מתנהגת כשמגיעים למגבלה. כדי להגדיר פרק זמן חדש לזמן קצוב, מריצים את הפקודה adb הבאה:

adb shell device_config put activity_manager data_sync_fgs_timeout_duration duration-in-milliseconds

New media processing foreground service type

ב-Android 15 מוצג סוג חדש של שירות שפועל בחזית, mediaProcessing. סוג השירות הזה מתאים לפעולות כמו המרת קידוד של קובצי מדיה. לדוגמה, אפליקציית מדיה עשויה להוריד קובץ אודיו ולהמיר אותו לפורמט אחר לפני ההפעלה. אתם יכולים להשתמש בשירות mediaProcessing שפועל בחזית כדי לוודא שההמרה תימשך גם כשהאפליקציה ברקע.

המערכת מאפשרת לשירותי mediaProcessing של אפליקציה לפעול במשך 6 שעות בסך הכול בתקופה של 24 שעות, ולאחר מכן המערכת קוראת ל-method‏ Service.onTimeout(int, int) של השירות שפועל (הmethod הזה הוצג ב-Android 15). בשלב הזה, לשירות יש כמה שניות לבצע קריאה ל-Service.stopSelf(). אם השירות לא קורא ל-Service.stopSelf(), המערכת גורמת לחריגה פנימית. החריג מתועד ביומן Logcat עם ההודעה הבאה:

Fatal Exception: android.app.RemoteServiceException: "A foreground service of
type mediaProcessing did not stop within its timeout: [component name]"

כדי למנוע את החריגה, אפשר לבצע אחת מהפעולות הבאות:

  1. עליך להטמיע בשירות שלך את השיטה החדשה של Service.onTimeout(int, int). כשהאפליקציה מקבלת את הקריאה החוזרת, חשוב להקפיד להתקשר ל-stopSelf() תוך מספר שניות. (אם לא מפסיקים את האפליקציה מיד, המערכת יוצרת כשל).
  2. חשוב לוודא ששירותי mediaProcessing של האפליקציה לא פועלים במשך יותר מ-6 שעות בסך הכול בכל תקופה של 24 שעות (אלא אם המשתמש יוצר אינטראקציה עם האפליקציה, ומאפס את הטיימר).
  3. כדאי להפעיל שירותים mediaProcessing שפועלים בחזית רק כתוצאה מאינטראקציה ישירה של משתמש. מכיוון שהאפליקציה נמצאת בחזית כשהשירות מופעל, השירות מקבל את שש השעות המלאות אחרי שהאפליקציה עוברת לרקע.
  4. במקום להשתמש בשירות שפועל בחזית mediaProcessing, צריך להשתמש בAPI חלופי, כמו WorkManager.

אם שירותי mediaProcessing של האפליקציה פעלו בחזית במשך 6 שעות ב-24 השעות האחרונות, לא תוכלו להפעיל שירות mediaProcessing נוסף בחזית אלא אם המשתמש העביר את האפליקציה לחזית (פעולה שמאפסת את הטיימר). אם תנסו להפעיל שירות mediaProcessing שפועל בחזית, המערכת תשליך את הערך ForegroundServiceStartNotAllowedException עם הודעת שגיאה כמו "Time Limit כבר נשלחת לכל שירות שפועל בחזית מסוג mediaProcessing".

מידע נוסף על סוג השירות mediaProcessing זמין במאמר שינויים בסוגי שירותים בחזית במהדורת Android 15: עיבוד מדיה.

בדיקה

כדי לבדוק את התנהגות האפליקציה, אפשר להפעיל זמן קצוב לתפוגה של עיבוד מדיה גם אם האפליקציה לא מטרגטת ל-Android 15 (כל עוד האפליקציה פועלת במכשיר עם Android 15). כדי להפעיל זמן קצוב לתפוגה, מריצים את הפקודה הבאה ב-adb:

adb shell am compat enable FGS_INTRODUCE_TIME_LIMITS your-package-name

אפשר גם לשנות את משך הזמן של הזמן הקצוב לתפוגה, כדי שיהיה קל יותר לבדוק איך האפליקציה מתנהגת כשמגיעים למגבלה. כדי להגדיר פרק זמן חדש לתפוגה, מריצים את הפקודה הבאה של adb:

adb shell device_config put activity_manager media_processing_fgs_timeout_duration duration-in-milliseconds

Restrictions on BOOT_COMPLETED broadcast receivers launching foreground services

There are new restrictions on BOOT_COMPLETED broadcast receivers launching foreground services. BOOT_COMPLETED receivers are not allowed to launch the following types of foreground services:

If a BOOT_COMPLETED receiver tries to launch any of those types of foreground services, the system throws ForegroundServiceStartNotAllowedException.

Testing

To test your app's behavior, you can enable these new restrictions even if your app is not targeting Android 15 (as long as the app is running on an Android 15 device). Run the following adb command:

adb shell am compat enable FGS_BOOT_COMPLETED_RESTRICTIONS your-package-name

To send a BOOT_COMPLETED broadcast without restarting the device, run the following adb command:

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED your-package-name

Restrictions on starting foreground services while an app holds the SYSTEM_ALERT_WINDOW permission

בעבר, אם לאפליקציה הייתה הרשאה SYSTEM_ALERT_WINDOW, היא הייתה יכולה להפעיל שירות שפועל בחזית גם אם האפליקציה הייתה כרגע ברקע (כפי שמתואר בקטע פטורים מהגבלות הפעלה ברקע).

אם אפליקציה מטרגטת את Android 15, הפטור הזה מצומצם יותר עכשיו. עכשיו האפליקציה צריכה לקבל את ההרשאה SYSTEM_ALERT_WINDOW וגם להציג חלון שכבת-על גלוי. כלומר, האפליקציה צריכה קודם להפעיל חלון TYPE_APPLICATION_OVERLAY וגם החלון צריך להיות גלוי לפני שמפעילים שירות בחזית.

אם האפליקציה תנסה להפעיל שירות שפועל בחזית מהרקע בלי לעמוד בדרישות החדשות האלה (ואין לה פטור אחר), המערכת תשליך את השגיאה ForegroundServiceStartNotAllowedException.

אם האפליקציה שלכם מצהירה על ההרשאה SYSTEM_ALERT_WINDOW ומפעילה שירותים שפועלים בחזית מהרקע, היא עשויה להיות מושפעת מהשינוי הזה. אם האפליקציה מקבלת את השגיאה ForegroundServiceStartNotAllowedException, צריך לבדוק את סדר הפעולות של האפליקציה ולוודא שכבר יש לה חלון שכבת-על פעיל לפני שהיא מנסה להפעיל שירות בחזית מהרקע. תוכלו לבדוק אם החלון של שכבת-העל גלוי כרגע באמצעות קריאה ל-View.getWindowVisibility(), או לבטל את View.onWindowVisibilityChanged() כדי לקבל התראה בכל פעם שהחשיפה משתנה.

בדיקה

כדי לבדוק את התנהגות האפליקציה, אפשר להפעיל את ההגבלות החדשות האלה גם אם האפליקציה לא מטרגטת ל-Android 15 (כל עוד האפליקציה פועלת במכשיר עם Android 15). כדי להפעיל את ההגבלות החדשות האלה על הפעלת שירותים שפועלים בחזית מהרקע, מריצים את הפקודה הבאה של adb:

adb shell am compat enable FGS_SAW_RESTRICTIONS your-package-name

שינויים במועד שבו אפליקציות יכולות לשנות את המצב הגלובלי של המצב 'נא לא להפריע'

Apps that target Android 15 (API level 35) and higher 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 API

ב-Android 15 אנחנו ממשיכים את העבודה על רענון ספריות הליבה של Android כדי להתאים אותן לתכונות בגרסאות ה-LTS האחרונות של OpenJDK.

חלק מהשינויים האלה יכולים להשפיע על התאימות של אפליקציות שמטרגטות ל-Android 15 (רמת API 35):

  • שינויים בממשקי API לעיצוב מחרוזות: האימות של אינדקס הארגומנט, הדגלים, הרוחב והדיוק מחמיר יותר עכשיו כשמשתמשים בממשקי ה-API הבאים: String.format() ו-Formatter.format():

    לדוגמה, החריגה הבאה מופעלת כשמשתמשים באינדקס ארגומנט של 0 (%0 במחרוזת הפורמט):

    IllegalFormatArgumentIndexException: Illegal format argument index = 0
    

    במקרה כזה, אפשר לפתור את הבעיה באמצעות אינדקס ארגומנט של 1 (%1במחרוזת הפורמט).

  • שינויים בסוג הרכיב של Arrays.asList(...).toArray(): כשמשתמשים ב-Arrays.asList(...).toArray(), סוג הרכיב של המערך שמתקבל הוא עכשיו Object – ולא הסוג של הרכיבים במערך הבסיסי. לכן הקוד הבא מחזיר ClassCastException:

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

    במקרה כזה, כדי לשמור על String כסוג הרכיב במערך שמתקבל, אפשר להשתמש ב-Collection.toArray(Object[]) במקום:

    String[] elements = Arrays.asList("two", "one").toArray(new String[0]);
    
  • שינויים בטיפול בקוד שפה: כשמשתמשים ב-API‏ Locale, קודי השפה לעברית, יידיש ואינדונזית לא מומרים יותר לפורמטים מיושנים (עברית: iw, יידיש: ji ואינדונזית: in). כשמציינים את קוד השפה לאחד מהלוקאלים האלה, צריך להשתמש בקודים מ-ISO 639-1 (עברית: he, יידיש: yi ואינדונזית: id).

  • שינויים ברצפים של מספרים אקראיים: בעקבות השינויים שבוצעו בכתובת https://bugs.openjdk.org/browse/JDK-8301574, השיטות הבאות של Random.ints() מחזירות עכשיו רצף שונה של מספרים מהשיטות של Random.nextInt():

    בדרך כלל, השינוי הזה לא אמור לגרום להתנהגות שמשבשת את האפליקציה, אבל הקוד לא אמור לצפות שהרצף שנוצר משיטות Random.ints() יתאים ל-Random.nextInt().

ה-API החדש SequencedCollection יכול להשפיע על התאימות של האפליקציה אחרי עדכון compileSdk בתצורת ה-build של האפליקציה לשימוש ב-Android 15 (רמת API 35):

  • התנגשות עם פונקציות ההרחבה MutableList.removeFirst() ו-MutableList.removeLast() ב-kotlin-stdlib

    הסוג List ב-Java ממופה לסוג MutableList ב-Kotlin. ממשקי ה-API‏ List.removeFirst() ו-List.removeLast() הוצגו ב-Android 15 (רמת API 35), לכן קומפיילר Kotlin פותר קריאות לפונקציות, למשל list.removeFirst(), באופן סטטי לממשקי ה-API החדשים List במקום לפונקציות ההרחבה ב-kotlin-stdlib.

    אם קומפלו מחדש אפליקציה עם compileSdk שהוגדר ל-35 ועם minSdk שהוגדר ל-34 או לגרסה מוקדמת יותר, ואז האפליקציה מופעלת ב-Android 14 ובגרסאות קודמות, מוצגת שגיאת זמן ריצה:

    java.lang.NoSuchMethodError: No virtual method
    removeFirst()Ljava/lang/Object; in class Ljava/util/ArrayList;
    

    אפשר להשתמש באפשרות NewApi lint הקיימת בפלאגין של Android Gradle כדי לזהות את השימושים החדשים ב-API.

    ./gradlew lint
    
    MainActivity.kt:41: Error: Call requires API level 35 (current min is 34): java.util.List#removeFirst [NewApi]
          list.removeFirst()
    

    כדי לתקן את חריגת זמן הריצה ואת שגיאות ה-lint, אפשר להחליף את הקריאות לפונקציות removeFirst() ו-removeLast() ב-Kotlin בפונקציות removeAt(0) ו-removeAt(list.lastIndex) בהתאמה. אם אתם משתמשים ב-Android Studio Ladybug | 2024.1.3 או בגרסה מתקדמת יותר, מוצגת גם אפשרות לתיקון מהיר של השגיאות האלה.

    אם האפשרות lint מושבתת, כדאי להסיר את התווים @SuppressLint("NewApi") ו-lintOptions { disable 'NewApi' }.

  • התנגשות עם שיטות אחרות ב-Java

    נוספו methods חדשות לסוגים הקיימים, למשל, List ו-Deque. יכול להיות שהשיטות החדשות לא יהיו תואמות לשיטות עם אותו שם וסוגי ארגומנטים בממשקים ובמחלוקות אחרים. במקרה של התנגשות בחתימת שיטה עם אי-תאימות, מהדר javac מוציא שגיאה בזמן הבנייה. לדוגמה:

    דוגמה לשגיאה 1:

    javac MyList.java
    
    MyList.java:135: error: removeLast() in MyList cannot implement removeLast() in List
      public void removeLast() {
                  ^
      return type void is not compatible with Object
      where E is a type-variable:
        E extends Object declared in interface List
    

    שגיאה לדוגמה 2:

    javac MyList.java
    
    MyList.java:7: error: types Deque<Object> and List<Object> are incompatible;
    public class MyList implements  List<Object>, Deque<Object> {
      both define reversed(), but with unrelated return types
    1 error
    

    שגיאה לדוגמה 3:

    javac MyList.java
    
    MyList.java:43: error: types List<E#1> and MyInterface<E#2> are incompatible;
    public static class MyList implements List<Object>, MyInterface<Object> {
      class MyList inherits unrelated defaults for getFirst() from types List and MyInterface
      where E#1,E#2 are type-variables:
        E#1 extends Object declared in interface List
        E#2 extends Object declared in interface MyInterface
    1 error
    

    כדי לתקן את שגיאות ה-build האלה, המחלקה שמטמיעה את הממשקים האלה צריכה לבטל את השיטה עם סוג החזרה תואם. לדוגמה:

    @Override
    public Object getFirst() {
        return List.super.getFirst();
    }
    

אבטחה

‫Android 15 כולל שינויים שמקדמים את אבטחת המערכת כדי לעזור להגן על אפליקציות ומשתמשים מפני אפליקציות זדוניות.

גרסאות TLS מוגבלות

ב-Android 15 יש הגבלה על השימוש ב-TLS בגרסאות 1.0 ו-1.1. הגרסאות האלה הוצאו משימוש ב-Android, אבל עכשיו אסור להשתמש בהן באפליקציות שמטרגטות את Android 15.

הפעלות מאובטחות של פעילות ברקע

Android 15 protects users from malicious apps and gives them more control over their devices by adding changes that prevent malicious background apps from bringing other apps to the foreground, elevating their privileges, and abusing user interaction. Background activity launches have been restricted since Android 10 (API level 29).

Other changes

  • Change PendingIntent creators to block background activity launches by default. This helps prevent apps from accidentally creating a PendingIntent that could be abused by malicious actors.
  • Don't bring an app to the foreground unless the PendingIntent sender allows it. This change aims to prevent malicious apps from abusing the ability to start activities in the background. By default, apps are not allowed to bring the task stack to the foreground unless the creator allows background activity launch privileges or the sender has background activity launch privileges.
  • Control how the top activity of a task stack can finish its task. If the top activity finishes a task, Android will go back to whichever task was last active. Moreover, if a non-top activity finishes its task, Android will go back to the home screen; it won't block the finish of this non-top activity.
  • Prevent launching arbitrary activities from other apps into your own task. This change prevents malicious apps from phishing users by creating activities that appear to be from other apps.
  • Block non-visible windows from being considered for background activity launches. This helps prevent malicious apps from abusing background activity launches to display unwanted or malicious content to users.

כוונות רכישה בטוחות יותר

Android 15 introduces StrictMode for intents.

In order to see detailed logs about Intent usage violations, use following method:

Kotlin

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

Java

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

חוויית המשתמש וממשק המשתמש של המערכת

‫Android 15 כוללת כמה שינויים שנועדו ליצור חוויית משתמש עקבית ואינטואיטיבית יותר.

שינויים בהזחה של חלון

There are two changes related to window insets in Android 15: edge-to-edge is enforced by default, and there are also configuration changes, such as the default configuration of system bars.

אכיפה מקצה לקצה

אפליקציות מוצגות מקצה לקצה כברירת מחדל במכשירים שפועלים עם Android 15 אם האפליקציה מטרגטת ל-Android 15 (רמת API 35).

אפליקציה שמטרגטת ל-Android 14 ולא מוצגת מקצה לקצה במכשיר עם Android 15.


אפליקציה שמטרגטת ל-Android 15 (רמת API 35) ומוצגת מקצה לקצה במכשיר עם Android 15. האפליקציה הזו משתמשת בעיקר ברכיבי Material 3 Compose שמחילים באופן אוטומטי שוליים פנימיים. המסך הזה לא מושפע לרעה מהאכיפה של תצוגה מקצה לקצה ב-Android 15.

זהו שינוי שעלול לגרום לבעיות, ויכול להיות שתהיה לו השפעה שלילית על ממשק המשתמש של האפליקציה. השינויים ישפיעו על האזורים הבאים בממשק המשתמש:

  • סרגל ניווט עם ידית לתנועות
    • שקוף כברירת מחדל.
    • ההיסט התחתון מושבת, ולכן התוכן מוצג מאחורי סרגל הניווט של המערכת, אלא אם מוחלים שוליים פנימיים.
    • האפשרויות setNavigationBarColor ו-R.attr#navigationBarColor הוצאו משימוש ולא משפיעות על הניווט באמצעות תנועות.
    • setNavigationBarContrastEnforced ו-R.attr#navigationBarContrastEnforced ממשיכים שלא להשפיע על הניווט באמצעות מחוות.
  • ניווט ב-3 כפתורים
    • השקיפות מוגדרת כברירת מחדל ל-80%, והצבע יכול להיות זהה לצבע הרקע של החלון.
    • ההיסט התחתון מושבת, כך שהתוכן מוצג מאחורי סרגל הניווט של המערכת, אלא אם מוחלים שוליים פנימיים.
    • setNavigationBarColor ו-R.attr#navigationBarColor מוגדרים כברירת מחדל כך שיתאימו לרקע של החלון. כדי שברירת המחדל הזו תחול, הרקע של החלון צריך להיות פריט גרפי שניתן להזזה בצבע. ממשק ה-API הזה הוצא משימוש, אבל הוא ממשיך להשפיע על ניווט ב-3 כפתורים.
    • הערך של setNavigationBarContrastEnforced ושל R.attr#navigationBarContrastEnforced הוא TRUE כברירת מחדל, ולכן מתווסף רקע אטום ב-80% לניווט ב-3 כפתורים.
  • שורת הסטטוס
    • שקוף כברירת מחדל.
    • ההיסט העליון מושבת, ולכן התוכן מוצג מאחורי שורת הסטטוס, אלא אם מוחלים שוליים פנימיים.
    • setStatusBarColor ו-R.attr#statusBarColor הוצאו משימוש ואין להם השפעה על Android 15.
    • setStatusBarContrastEnforced ו-R.attr#statusBarContrastEnforced הוצאו משימוש, אבל עדיין יש להם השפעה ב-Android 15.
  • מגרעת במסך
    • הערך של layoutInDisplayCutoutMode בחלונות לא צפים צריך להיות LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS. ‫SHORT_EDGES, ‏NEVER ו-DEFAULT מוצגים כ-ALWAYS כדי שהמשתמשים לא יראו פס שחור שנוצר בגלל מגרעת במסך, והם יופיעו מקצה לקצה.

בדוגמה הבאה מוצגת אפליקציה לפני ואחרי טירגוט ל-Android 15 (רמת API‏ 35), ולפני ואחרי החלת אזורי inset. הדוגמה הזו לא מקיפה, והמראה שלה עשוי להיות שונה ב-Android Auto.

אפליקציה שמטרגטת ל-Android 14 ולא מוצגת מקצה לקצה במכשיר עם Android 15.
אפליקציה שמטרגטת ל-Android 15 (רמת API 35) ומוצגת מקצה לקצה במכשיר עם Android 15. עם זאת, הרבה רכיבים מוסתרים עכשיו על ידי שורת הסטטוס, ניווט ב-3 כפתורים או מגרעת במסך, בגלל האכיפה של תצוגה מקצה לקצה ב-Android 15. ממשק משתמש מוסתר כולל את סרגל האפליקציות העליון של Material 2, כפתורי פעולה צפים ופריטים ברשימה.
אפליקציה שמטרגטת ל-Android 15 (רמת API 35), מוצגת מקצה לקצה במכשיר Android 15 ומחילת שוליים פנימיים כך שממשק המשתמש לא מוסתר.
מה צריך לבדוק אם האפליקציה כבר מוצגת מקצה לקצה

אם האפליקציה שלכם כבר מכסה את כל המסך ומוגדרים בה שוליים פנימיים, השינוי לא ישפיע עליה ברוב המקרים, למעט בתרחישים הבאים. עם זאת, גם אם אתם חושבים שהאפליקציה שלכם לא מושפעת, מומלץ לבדוק אותה.

  • יש לכם חלון לא צף, כמו Activity שמשתמש ב-SHORT_EDGES, ב-NEVER או ב-DEFAULT במקום ב-LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS. אם האפליקציה קורסת בזמן ההפעלה, יכול להיות שהבעיה היא במסך הפתיחה. אפשר לשדרג את התלות ב-core splashscreen לגרסה 1.2.0-alpha01 ומעלה, או להגדיר את window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutInDisplayCutoutMode.always.
  • יכול להיות שיהיו מסכים עם תנועת גולשים נמוכה יותר שבהם ממשק המשתמש מוסתר. מוודאים שרכיבי ממשק המשתמש במסכים האלה, שפחות מבקרים בהם, לא מוסתרים. מסכים עם נפח תנועה נמוך יותר כוללים:
    • מסכי צירוף או כניסה
    • דפי הגדרות
מה צריך לבדוק אם האפליקציה לא מוצגת מקצה לקצה

אם האפליקציה שלכם עדיין לא מוצגת מקצה לקצה, סביר להניח שאתם מושפעים מהשינוי. בנוסף לתרחישים של אפליקציות שכבר מוצגות מקצה לקצה, כדאי לשקול את הדברים הבאים:

  • אם האפליקציה שלכם משתמשת ברכיבי Material 3 ‏( androidx.compose.material3) ב-Compose, כמו TopAppBar,‏ BottomAppBar ו-NavigationBar, סביר להניח שהרכיבים האלה לא יושפעו כי הם מטפלים באופן אוטומטי ב-insets.
  • אם האפליקציה שלכם משתמשת ברכיבי Material 2 ‏( androidx.compose.material) ב-Compose, הרכיבים האלה לא מטפלים באופן אוטומטי ב-insets. עם זאת, אפשר לקבל גישה לתמונות הממוזערות ולהוסיף אותן באופן ידני. ב-androidx.compose.material מגרסה 1.6.0 ואילך, אפשר להשתמש בפרמטר windowInsets כדי להחיל את השוליים הפנימיים באופן ידני על BottomAppBar, TopAppBar, BottomNavigation ו-NavigationRail. באופן דומה, משתמשים בפרמטר contentWindowInsets עבור Scaffold.
  • אם האפליקציה שלכם משתמשת בתצוגות ורכיבי Material (‏com.google.android.material), רוב רכיבי Material מבוססי-תצוגות, כמו BottomNavigationView,‏ BottomAppBar, ‏ NavigationRailView או NavigationView, מטפלים בשוליים הפנימיים ולא דורשים עבודה נוספת. עם זאת, אם משתמשים ב-AppBarLayout, צריך להוסיף android:fitsSystemWindows="true".
  • לרכיבי composable מותאמים אישית, צריך להחיל את השוליים הפנימיים באופן ידני כריפוד. אם התוכן נמצא בתוך Scaffold, אפשר להשתמש בערכי הריווח הפנימי של Scaffold כדי להגדיר את השוליים הפנימיים. אחרת, מוסיפים ריווח באמצעות אחת מהאפשרויות של WindowInsets.
  • אם האפליקציה שלכם משתמשת בתצוגות וב-BottomSheet, ב-SideSheet או במאגרי תגים בהתאמה אישית, צריך להחיל ריווח פנימי באמצעות ViewCompat.setOnApplyWindowInsetsListener. ב-RecyclerView, מוסיפים מרווח פנימי באמצעות מאזין זה וגם מוסיפים clipToPadding="false".
מה צריך לבדוק אם האפליקציה חייבת להציע הגנה מותאמת אישית ברקע

אם האפליקציה שלכם צריכה להציע הגנה מותאמת אישית ברקע לניווט ב-3 כפתורים או לשורת הסטטוס, האפליקציה צריכה למקם רכיב שאפשר להרכיב או תצוגה מאחורי סרגל המערכת באמצעות WindowInsets.Type#tappableElement() כדי לקבל את הגובה של סרגל הניווט עם 3 הלחצנים או WindowInsets.Type#statusBars.

מקורות מידע נוספים על תצוגה מקצה לקצה

במאמרים Edge to Edge Views ו-Edge to Edge Compose מפורטים שיקולים נוספים לגבי החלת שוליים פנימיים.

ממשקי API שהוצאו משימוש

ממשקי ה-API הבאים הוצאו משימוש אבל לא הושבתו:

ממשקי ה-API הבאים הוצאו משימוש והושבתו:

הגדרה יציבה

אם האפליקציה מטרגטת ל-Android 15 (רמת API 35) ומעלה, התג Configuration כבר לא מחריג את סרגלי המערכת. אם אתם משתמשים בגודל המסך במחלקה Configuration לחישוב הפריסה, כדאי להחליף אותו בחלופות טובות יותר כמו ViewGroup, WindowInsets או WindowMetricsCalculator, בהתאם לצורך.

Configuration זמין החל מ-API 1. בדרך כלל הוא מתקבל מ-Activity.onConfigurationChanged. הוא מספק מידע כמו צפיפות החלונות, הכיוון והגדלים. מאפיין חשוב לגבי גדלי החלונות שמוחזרים מ-Configuration הוא שבעבר לא נכללו בהם סרגלי המערכת.

גודל ההגדרה משמש בדרך כלל לבחירת משאבים, כמו /res/layout-h500dp, וזה עדיין תרחיש שימוש תקף. עם זאת, תמיד המלצנו שלא להשתמש בו לחישוב פריסה. אם כן, כדאי להתרחק ממנו עכשיו. צריך להחליף את השימוש ב-Configuration במשהו מתאים יותר בהתאם לתרחיש לדוגמה.

אם משתמשים בו לחישוב הפריסה, צריך להשתמש ב-ViewGroup מתאים, כמו CoordinatorLayout או ConstraintLayout. אם משתמשים בו כדי לקבוע את הגובה של סרגל הניווט של המערכת, צריך להשתמש ב-WindowInsets. אם רוצים לדעת מה הגודל הנוכחי של חלון האפליקציה, משתמשים ב-computeCurrentWindowMetrics.

ברשימה הבאה מפורטים השדות שיושפעו מהשינוי הזה:

  • הגודל של Configuration.screenWidthDp ושל screenHeightDp כבר לא כולל את סרגלי המערכת.
  • השינויים ב-screenWidthDp וב-screenHeightDp משפיעים באופן עקיף על Configuration.smallestScreenWidthDp.
  • השינויים ב-screenWidthDp וב-screenHeightDp משפיעים באופן עקיף על Configuration.orientation במכשירים שהיחס בין האורך לרוחב שלהם קרוב ל-1:1.
  • השינויים ב-Configuration משפיעים באופן עקיף על Display.getSize(Point). החל מרמת API‏ 30, השיטה הזו הוצאה משימוש.
  • Display.getMetrics() כבר פועל כך החל מרמת API‏ 33.

ערך ברירת המחדל של המאפיין elegantTextHeight הוא true

For apps targeting Android 15 (API level 35), the elegantTextHeight TextView attribute becomes true by default, replacing the compact font used by default with some scripts that have large vertical metrics with one that is much more readable. The compact font was introduced to prevent breaking layouts; Android 13 (API level 33) prevents many of these breakages by allowing the text layout to stretch the vertical height utilizing the fallbackLineSpacing attribute.

In Android 15, the compact font still remains in the system, so your app can set elegantTextHeight to false to get the same behavior as before, but it is unlikely to be supported in upcoming releases. So, if your app supports the following scripts: Arabic, Lao, Myanmar, Tamil, Gujarati, Kannada, Malayalam, Odia, Telugu or Thai, test your app by setting elegantTextHeight to true.

elegantTextHeight behavior for apps targeting Android 14 (API level 34) and lower.
elegantTextHeight behavior for apps targeting Android 15.

שינויים ברוחב של TextView עבור צורות מורכבות של אותיות

בגרסאות קודמות של Android, חלק מגופנים או שפות עם כתב מחובר עם עיצוב מורכב עשויים לצייר את האותיות באזור של התו הקודם או הבא. במקרים מסוימים, אותיות כאלה נחתכו בנקודת ההתחלה או הסיום. החל מ-Android 15, TextView מקצה רוחב לציור מספיק מקום לאותיות כאלה ומאפשר לאפליקציות לבקש תוספת רווח שמימין כדי למנוע חיתוך.

מכיוון שהשינוי הזה משפיע על האופן שבו TextView מחליט על הרוחב, TextView מקצה יותר רוחב כברירת מחדל אם האפליקציה מטרגטת ל-Android 15 (רמת API 35) ואילך. אפשר להפעיל או להשבית את ההתנהגות הזו על ידי שליחת קריאה ל-API setUseBoundsForWidth ב-TextView.

הוספת רווח פנימי בצד ימין עלולה לגרום לחוסר התאמה של פריסות קיימות, ולכן הוא לא מתווסף כברירת מחדל גם לאפליקציות שמטרגטות את 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 בהתאם ללוקאל

In previous versions of Android, the text layout stretched the height of the text to meet the line height of the font that matched the current locale. For example, if the content was in Japanese, because the line height of the Japanese font is slightly larger than the one of a Latin font, the height of the text became slightly larger. However, despite these differences in line heights, the EditText element was sized uniformly, regardless of the locale being used, as illustrated in the following image:

Three boxes representing EditText elements that can contain text from English (en), Japanese (ja), and Burmese (my). The height of the EditText is the same, even though these languages have different line heights from each other.

For apps targeting Android 15 (API level 35), a minimum line height is now reserved for EditText to match the reference font for the specified Locale, as shown in the following image:

Three boxes representing EditText elements that can contain text from English (en), Japanese (ja), and Burmese (my). The height of the EditText now includes space to accommodate the default line height for these languages' fonts.

If needed, your app can restore the previous behavior by specifying the useLocalePreferredLineHeightForMinimum attribute to false, and your app can set custom minimum vertical metrics using the setMinimumFontMetrics API in Kotlin and Java.

מצלמה ומדיה

ב-Android 15 בוצעו השינויים הבאים בהתנהגות של המצלמה והמדיה באפליקציות שמטרגטות את Android 15 ואילך.

הגבלות על בקשות למיקוד אודיו

כדי לבקש את המיקוד באודיו, אפליקציות שמטרגטות את Android 15 (רמת API‏ 35) צריכות להיות האפליקציה העליונה או להפעיל שירות בחזית. אם אפליקציה מנסה לבקש להתמקד בה כשהיא לא עומדת באחת מהדרישות האלה, הקריאה מחזירה את הערך AUDIOFOCUS_REQUEST_FAILED.

מידע נוסף על התכונה 'מיקוד אודיו' זמין במאמר ניהול התכונה 'מיקוד אודיו'.

הגבלות שאינן קשורות ל-SDK עודכנו

‫Android 15 כולל רשימות מעודכנות של ממשקי non-SDK מוגבלים, שמבוססות על שיתוף פעולה עם מפתחי Android ועל הבדיקות הפנימיות האחרונות. כשאפשר, אנחנו מוודאים שיש חלופות ציבוריות לפני שאנחנו מגבילים ממשקים שאינם ב-SDK.

אם האפליקציה שלכם לא מטרגטת ל-Android 15, יכול להיות שחלק מהשינויים האלה לא ישפיעו עליכם באופן מיידי. עם זאת, יכול להיות שהאפליקציה שלך תוכל לגשת לחלק מממשקי ה-API שאינם חלק מ-SDK, בהתאם לרמת ה-API לטירגוט של האפליקציה. אבל שימוש בשיטה או בשדה כלשהם שאינם חלק מ-SDK תמיד כרוך בסיכון גבוה לגרימת נזק לאפליקציה.

אם אתם לא בטוחים אם האפליקציה שלכם משתמשת בממשקים שאינם SDK, אתם יכולים לבצע בדיקה לאפליקציה כדי לגלות זאת. אם האפליקציה שלכם מסתמכת על ממשקים שאינם SDK, כדאי להתחיל לתכנן מעבר לחלופות SDK. עם זאת, אנחנו מבינים שיש אפליקציות שבהן יש תרחישי שימוש לגיטימיים בממשקים שאינם SDK. אם אין לך אפשרות להשתמש בממשק שאינו SDK עבור תכונה באפליקציה, עליך לשלוח בקשה ליצירת API ציבורי חדש.

מידע נוסף על השינויים בגרסה הזו של Android זמין במאמר עדכונים לגבי הגבלות על ממשקים שאינם SDK ב-Android 15. מידע נוסף על ממשקים שאינם ב-SDK זמין במאמר הגבלות על ממשקים שאינם ב-SDK.