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

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

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

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

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

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

אנחנו מבצעים את השינויים הבאים בשירותים שפועלים בחזית ב-Android 15.

התנהגות זמן קצוב לתפוגה של שירות בחזית לסנכרון נתונים

ב-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

סוג חדש של שירות שפועל בחזית לעיבוד מדיה

ב-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

הגבלות על מקלטי שידורים מסוג BOOT_COMPLETED שמפעילים שירותים שפועלים בחזית

יש הגבלות חדשות על השקת מקלטי שידור של BOOT_COMPLETED שירותים שפועלים בחזית. למקלטי BOOT_COMPLETED אסור להפעיל את הסוגים הבאים של שירותים שפועלים בחזית:

אם מקלט BOOT_COMPLETED מנסה להפעיל אחד מהסוגים האלה של חזית השירותים האלה, המערכת מטילה ForegroundServiceStartNotAllowedException.

בדיקה

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

adb shell am compat enable FGS_BOOT_COMPLETED_RESTRICTIONS your-package-name

כדי לשלוח שידור של BOOT_COMPLETED בלי להפעיל מחדש את המכשיר: מריצים את הפקודה הבאה של adb:

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

הגבלות על הפעלת שירותים שפועלים בחזית בזמן שאפליקציה מחזיקה בהרשאה SYSTEM_ALERT_WINDOW

בעבר, אם לאפליקציה הייתה הרשאה 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

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

以 Android 15(API 级别 35)及更高版本为目标平台的应用无法再更改设备上的勿扰 (DND) 功能的全局状态或政策(无论是通过修改用户设置还是关闭勿扰模式)。相反,应用必须提供 AutomaticZenRule,系统会将其与现有的“最严格的政策优先”方案合并为一个全局政策。对之前会影响全局状态的现有 API 的调用(setInterruptionFiltersetNotificationPolicy)会导致创建或更新隐式 AutomaticZenRule,该 AutomaticZenRule 会根据这些 API 调用的调用周期开启和关闭。

请注意,只有当应用调用 setInterruptionFilter(INTERRUPTION_FILTER_ALL) 并希望该调用停用之前由其所有者激活的 AutomaticZenRule 时,此更改才会影响可观察到的行为。

שינויים ב-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 בהגדרות הבנייה של האפליקציה לשימוש ב-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

    נוספו שיטות חדשות לסוגים הקיימים, לדוגמה, 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.

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

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

שינויים נוספים

בנוסף להגבלה על ההתאמה של UID, השינויים האלה כלול:

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

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

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

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

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

Kotlin

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

Java

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

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

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

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

Android 15 中与窗口内边距相关的两项变更:默认强制执行边到边,此外还有配置变更,例如系统栏的默认配置。

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

אפליקציות מוצגות מקצה לקצה כברירת מחדל במכשירים שפועלים עם 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.

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

במאמרים תצוגות מקצה לקצה ויצירת קומפוזיציה מקצה לקצה מפורטים שיקולים נוספים לגבי החלת שוליים פנימיים.

ממשקי 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.
  • Display.getSize(Point) מושפע באופן עקיף מהשינויים ב-Configuration. השימוש בשיטה הזו הוצא משימוש החל מרמת API‏ 30.
  • Display.getMetrics() כבר פועל כך מרמת API‏ 33.

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

באפליקציות שמטרגטות את Android 15 (רמת API 35), המאפיין TextView של elegantTextHeight הופך ל-true כברירת מחדל, ומחליף את הגופן הקומפקטי שמשמש כברירת מחדל בסקריפטים מסוימים עם מדדים אנכיים גדולים, בגופן שקל יותר לקרוא אותו. הגופן הקומפקטי הוצג כדי למנוע הפרעה לפריסות. בגרסה Android 13 (רמת API 33) מונעים הרבה מהפרעות כאלה על ידי מתן אפשרות לפריסת הטקסט להתמתח לגובה האנכי באמצעות המאפיין fallbackLineSpacing.

ב-Android 15, הגופן הקומפקטי עדיין נשאר במערכת, כך שהאפליקציה יכולה להגדיר את elegantTextHeight ל-false כדי לקבל את אותה התנהגות כמו קודם, אבל סביר להניח שהוא לא יקבל תמיכה בגרסאות הבאות. לכן, אם האפליקציה תומכת בסקריפטים הבאים: ערבית, לאוס, מיאנמר, טמילית, גוג'ראטית, קנאדה, מליאלאם, אודיה, טלוגו או תאית, צריך לבדוק את האפליקציה על ידי הגדרת elegantTextHeight לערך true.

התנהגות של
elegantTextHeight באפליקציות שמיועדות ל-Android 14 (רמת API ‏34) וגרסאות ישנות יותר.
התנהגות של elegantTextHeight באפליקציות שמטרגטות את Android 15.

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

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

由于此更改会影响 TextView 确定宽度的方式,因此如果应用以 Android 15(API 级别 35)或更高版本为目标平台,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 (רמת API 35), גובה שורה מינימלי מוקצה עכשיו ל-EditText כדי להתאים לגופן העזר של האזור הגיאוגרפי שצוין, כפי שמוצג בתמונה הבאה:

שלוש תיבות שמייצגות רכיבי EditText שיכולים להכיל טקסט באנגלית (en), ביפנית (ja) ובבורמזית (my). הגובה של EditText כולל עכשיו מקום שמתאים לגובה השורה שמוגדר כברירת מחדל לגופנים של השפות האלה.

אם צריך, אפשר לשחזר את ההתנהגות הקודמת של האפליקציה על ידי ציון הערך false למאפיין useLocalePreferredLineHeightForMinimum. אפשר גם להגדיר באפליקציה מדדים מינימליים מותאמים אישית של מודעות רגילות באמצעות ממשק ה-API setMinimumFontMetrics ב-Kotlin וב-Java.

מצלמה ומדיה

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

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

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

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

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

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

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

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

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