בדומה לגרסאות קודמות, Android 15 כולל שינויים בהתנהגות שעשויים להשפיע על האפליקציה שלכם. שינויי ההתנהגות הבאים חלים אך ורק על אפליקציות שמטרגטות ל-Android 15 ואילך. אם האפליקציה שלכם מטרגטת את Android מגרסה 15 ואילך, עליכם לשנות את האפליקציה כך שתתמוך בהתנהגויות האלה בצורה תקינה, במקרים הרלוונטיים.
חשוב גם לבדוק את רשימת השינויים בהתנהגות שמשפיעים על כל האפליקציות שפועלות ב-Android 15, ללא קשר ל-targetSdkVersion
של האפליקציה.
פונקציונליות עיקרית
ב-Android 15 יש שינויים או הרחבות של יכולות ליבה שונות במערכת Android.
שינויים בשירותים שפועלים בחזית
אנחנו מבצעים את השינויים הבאים בשירותים שפועלים בחזית ב-Android 15.
- התנהגות הזמן הקצוב לתפוגה של שירות שפועל בחזית של סנכרון הנתונים
- סוג חדש של שירות שפועל בחזית עיבוד מדיה
- הגבלות על הפעלת שירותים שפועלים בחזית של
BOOT_COMPLETED
מקלטי שידור - הגבלות על הפעלת שירותים שפועלים בחזית כל עוד האפליקציה עם ההרשאה
SYSTEM_ALERT_WINDOW
התנהגות של זמן קצוב לתפוגה של שירות שפועל בחזית של סנכרון נתונים
ב-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]"
כדי למנוע בעיות בעקבות השינוי הזה בהתנהגות, תוכלו לבצע אחת או יותר מהפעולות הבאות:
- מטמיעים את השיטה החדשה
Service.onTimeout(int, int)
בשירות. כשהאפליקציה תקבל את הקריאה החוזרת, חשוב להתקשר למספרstopSelf()
תוך כמה שניות. (אם לא עוצרים את האפליקציה מיד, המערכת יוצרת כשל). - חשוב לוודא ששירותי
dataSync
של האפליקציה לא פועלים במשך יותר מ-6 שעות בסך הכול בכל תקופה של 24 שעות (אלא אם המשתמש יוצר אינטראקציה עם האפליקציה, ומאפס את הטיימר). - כדאי להפעיל שירותים
dataSync
שפועלים בחזית רק כתוצאה מאינטראקציה ישירה של משתמש. מכיוון שהאפליקציה נמצאת בחזית כשהשירות מופעל, השירות מקבל את שש השעות המלאות אחרי שהאפליקציה עוברת לרקע. - במקום להשתמש בשירות שפועל בחזית
dataSync
, צריך להשתמש בAPI חלופי.
אם שירותי dataSync
של האפליקציה פעלו בחזית במשך 6 שעות ב-24 השעות האחרונות, לא תוכלו להפעיל שירות dataSync
נוסף בחזית אלא אם המשתמש העביר את האפליקציה לחזית (פעולה שמאפסת את הטיימר). אם תנסו להפעיל שירות dataSync
נוסף שפועל בחזית, המערכת תשליך את האירוע ForegroundServiceStartNotAllowedException
עם הודעת שגיאה כמו "Time limit already exhausted for foreground service type dataSync".
בדיקה
כדי לבדוק את התנהגות האפליקציה, אפשר להפעיל זמן קצוב לסיום הסנכרון של הנתונים גם אם האפליקציה לא מטרגטת ל-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]"
כדי למנוע את החריגה, אפשר לבצע אחת מהפעולות הבאות:
- עליך להטמיע בשירות שלך את השיטה החדשה של
Service.onTimeout(int, int)
. כשהאפליקציה מקבלת את הקריאה החוזרת, חשוב להקפיד להתקשר ל-stopSelf()
תוך מספר שניות. (אם לא מפסיקים את האפליקציה מיד, המערכת יוצרת כשל). - חשוב לוודא ששירותי
mediaProcessing
של האפליקציה לא פועלים במשך יותר מ-6 שעות בסך הכול בכל תקופה של 24 שעות (אלא אם המשתמש יוצר אינטראקציה עם האפליקציה, ומאפס את הטיימר). - כדאי להפעיל שירותים
mediaProcessing
שפועלים בחזית רק כתוצאה מאינטראקציה ישירה של משתמש. מכיוון שהאפליקציה נמצאת בחזית כשהשירות מופעל, השירות מקבל את שש השעות המלאות אחרי שהאפליקציה עוברת לרקע. - במקום להשתמש בשירות שפועל בחזית
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
מקלטי שידורים
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:
dataSync
camera
mediaPlayback
phoneCall
mediaProjection
microphone
(this restriction has been in place formicrophone
since Android 14)
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
הגבלות על הפעלת שירותים שפועלים בחזית בזמן שאפליקציה מסוימת מחזיקה בהרשאה 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.
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). To enable these new restrictions on starting foreground services
from the background, run the following adb
command:
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()
:String.format(String, Object[])
String.format(Locale, String, Object[])
Formatter.format(String, Object[])
Formatter.format(Locale, String, Object[])
לדוגמה, חריגה מהסוג הבא מתרחשת כשמשתמשים במדד ארגומנט של 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. מאחר שממשקי ה-APIList.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;
אפשרות ה-lint הקיימת
NewApi
בפלאגין של 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 ואילך, יש גם אפשרות לתיקון מהיר של השגיאות האלה.אם אפשרות האיתור של שגיאות בקוד מושבתת, כדאי להסיר את
@SuppressLint("NewApi")
ואתlintOptions { disable 'NewApi' }
.התנגשות עם שיטות אחרות ב-Java
נוספו שיטות חדשות לסוגי הנתונים הקיימים, למשל
List
ו-Deque
. יכול להיות שהשיטות החדשות האלה לא יהיו תואמות לשיטות עם אותו שם וסוגים של ארגומנטים בממשקים ובכיתות אחרים. במקרה של התנגשות בחתימת שיטת עם אי-תאימות, המהדר שלjavac
יפיק שגיאה בזמן ה-build. לדוגמה:דוגמה לשגיאה 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 יש שינויים שמקדמים אבטחת מערכת כדי להגן על אפליקציות ומשתמשים מפני אפליקציות זדוניות.
השקות של פעילות מאובטחת ברקע
מערכת Android 15 מגינה על המשתמשים מפני אפליקציות זדוניות ומספקת להם יותר שליטה במכשירים שלהם על ידי הוספת שינויים שמונעים מאפליקציות רקע זדוניות הצגת אפליקציות אחרות לחזית, העלאת רמת ההרשאות שלהן וניצול לרעה לאינטראקציה של המשתמשים. ההפעלות של פעילות ברקע הוגבלו מאז Android 10 (רמת API 29).
חסימת האפשרות להפעיל פעילויות של אפליקציות שלא תואמות ל-UID המוביל במקבץ
אפליקציות זדוניות יכולות לבצע פעילות של אפליקציה אחרת במסגרת אותה משימה, ואז
שכבת-על מעל ויוצרת אשליה של להיות האפליקציה. המשימה הזו
פריצה" מתקפה עוקפת את ההגבלות הנוכחיות של הפעלת רקע כי הכול
מתרחשת בתוך אותה משימה גלויה. כדי למזער את הסיכון הזה, Android 15 מוסיפה
דגל שחוסם את ההפעלה של אפליקציות שלא תואמות את ה-UID המוביל בסטאק
פעילויות. כדי להצטרף לכל הפעילויות של האפליקציה, צריך לעדכן את
allowCrossUidActivitySwitchFromBelow
בקובץ AndroidManifest.xml
של האפליקציה:
<application android:allowCrossUidActivitySwitchFromBelow="false" >
אמצעי האבטחה החדשים פעילים אם כל התנאים הבאים יתקיימו:
- האפליקציה שמבצעת את ההשקה מטרגטת את Android 15.
- האפליקציה שנמצאת בראש מקבץ המשימות מטרגטת את Android 15.
- כל הפעילות הגלויה אושרה על ידי המשתמשים במסגרת אמצעי ההגנה החדשים
אם אמצעי האבטחה מופעלים, האפליקציות עשויות לחזור לדף הבית, האפליקציה האחרונה שנראית למשתמש, אם הם משלימים משימה משלהם.
שינויים נוספים
בנוסף להגבלה על ההתאמה של UID, השינויים האלה כלול:
- יש לשנות
PendingIntent
יוצרים כך שלחסום השקות של פעילויות ברקע על ידי ברירת מחדל. זה עוזר למנוע מאפליקציות ליצור בטעותPendingIntent
שעלולים להיות מנוצלים לרעה על ידי גורמים זדוניים. - לא מעבירים אפליקציה לחזית אלא אם השולח של
PendingIntent
מאפשר זאת. השינוי הזה נועד למנוע מאפליקציות זדוניות לנצל לרעה את היכולת להתחיל פעילויות ברקע. כברירת מחדל, מורשים להעביר את מקבץ המשימות לחזית, אלא אם היוצר מאפשר זאת הרשאות השקה של פעילות ברקע או שלשולח יש פעילות ברקע הרשאות השקה. - אתם שולטים באופן שבו הפעילות הראשית במקבץ המשימות יכולה להשלים את המשימה שלה. אם הפעילות המובילה מסתיימת, ו-Android יחזור לאחת מהמשימות שבוצעו פעילות אחרונה. בנוסף, אם משימה שאינה מובילה מסיימת את המשימה, מערכת Android לחזור למסך הבית. היא לא תחסום את הסיומת פעילות.
- למנוע אפשרות להפעיל פעילויות שרירותיות מאפליקציות אחרות במכשיר שלכם משימה זו. השינוי הזה מונע מאפליקציות זדוניות מפני פישינג על ידי יצירת פעילויות שנראות כאילו נשלחו מאפליקציות אחרות.
- חסימת חלונות שאינם נראים לעין כדי שלא יתייחסו לפעילות ברקע השקות. כך ניתן למנוע מאפליקציות זדוניות לנצל לרעה את הרקע מופעלת כדי להציג תוכן לא רצוי או זדוני למשתמשים.
כוונות רכישה בטוחות יותר
ב-Android 15 נוספו אמצעי אבטחה אופציונליים חדשים כדי לשפר את הבטיחות והעמידות של הכוונות. השינויים האלה נועדו למנוע נקודות חולשה פוטנציאליות ושימוש לרעה בכוונות שאפשר לנצל על ידי אפליקציות זדוניות. יש שני שיפורים עיקריים באבטחה של כוונות ב-Android 15:
- התאמה למסנני הכוונה של היעד: כוונות שמטרגטות רכיבים ספציפיים חייבות להתאים במדויק למפרטי מסנני הכוונה של היעד. אם שולחים כוונה להפעלת פעילות של אפליקציה אחרת, רכיב הכוונה ליעד צריך להתאים למסנני הכוונה שהוצהרו בפעילות המקבלת.
- ל-Intents חייבות להיות פעולות: Intents ללא פעולה לא יתאימו יותר למסנני 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 כולל כמה שינויים שנועדו ליצור חוויית משתמש עקבית ואינטואיטיבית יותר.
שינויים בהוספה של חלון
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).
זהו שינוי שעלול להשפיע לרעה על ממשק המשתמש של האפליקציה. השינויים משפיעים על אזורי ממשק המשתמש הבאים:
- סרגל הניווט של תנועת האחיזה
- לא סודי כברירת מחדל.
- ההזזה למטה מושבתת, כך שהתוכן מוצג מאחורי סרגל הניווט של המערכת, אלא אם מחילים חתימות.
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.
מה צריך לבדוק אם האפליקציה כבר מוצגת מקצה לקצה
אם האפליקציה שלכם כבר מקצה לקצה ומחילה כניסות קלט, בדרך כלל אין לכם השפעה, חוץ מאשר בתרחישים הבאים. עם זאת, גם אם לדעתכם האפליקציה שלכם לא מושפעת, מומלץ לבדוק אותה.
- יש לכם חלון לא צף, כמו
Activity
שמשתמש ב-SHORT_EDGES
, ב-NEVER
או ב-DEFAULT
במקום ב-LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
. אם האפליקציה קורסת בזמן ההפעלה, יכול להיות שהסיבה לכך היא מסך הפתיחה. אפשר לשדרג את התלות של core splashscreen ל-1.2.0-alpha01 או לגרסת build מאוחרת יותר, או להגדיר אתwindow.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutInDisplayCutoutMode.always
. - יכול להיות שיהיו מסכים עם תנועה נמוכה יותר עם ממשק משתמש חסום. מוודאים שהמסכים האלה, שבהם יש פחות ביקורים, לא מכסים את ממשק המשתמש. מסכים עם פחות תנועה:
- מסכי קליטה או כניסה
- דפי הגדרות
מה צריך לבדוק אם האפליקציה עדיין לא מוצגת מקצה לקצה
אם האפליקציה שלכם עדיין לא תואמת למסך מלא, סביר להניח שהיא מושפעת מהשינוי. בנוסף לתרחישים לאפליקציות שכבר מוצגות מקצה לקצה, כדאי לשקול את האפשרויות הבאות:
- אם האפליקציה שלכם משתמשת ברכיבי Material 3 (
androidx.compose.material3
) ב-Compose, כמוTopAppBar
,BottomAppBar
ו-NavigationBar
, סביר להניח שהרכיבים האלה לא מושפעים מהשינוי כי הם מטפלים באופן אוטומטי בהכנסות. - אם באפליקציה שלכם נעשה שימוש ברכיבי Material 2 (
androidx.compose.material
) ב-Compose, הרכיבים האלה לא מטפלים באופן אוטומטי בהכנסות. עם זאת, תוכלו לקבל גישה לתמונות הממוזערות ולהחיל אותן באופן ידני. ב-androidx.compose.material 1.6.0 ואילך, משתמשים בפרמטרwindowInsets
כדי להחיל את הרכיבים הפנימיים באופן ידני עבורBottomAppBar
,TopAppBar
,BottomNavigation
ו-NavigationRail
. באופן דומה, צריך להשתמש בפרמטרcontentWindowInsets
עבורScaffold
. - אם באפליקציה שלכם נעשה שימוש בתצוגות וברכיבי Material (
com.google.android.material
), רוב רכיבי Material שמבוססים על תצוגות, כמוBottomNavigationView
,BottomAppBar
, NavigationRailView
אוNavigationView
, מטפלים בהוספת רכיבים לחלק הפנימי של המסך ולא נדרשת עבודה נוספת. עם זאת, צריך להוסיף אתandroid:fitsSystemWindows="true"
אם משתמשים ב-AppBarLayout
. - בתכנים קומפוזביליים בהתאמה אישית, צריך להחיל את הרכיבים הפנימיים באופן ידני בתור מרווח פנימי. אם התוכן שלכם נמצא בתוך
Scaffold
, תוכלו להשתמש ב-insets באמצעות ערכי המילוי שלScaffold
. אחרת, צריך להוסיף את ה-padding באמצעות אחת מהפונקציותWindowInsets
. - אם האפליקציה שלכם משתמשת בתצוגות ובמאגרי תגים מסוג
BottomSheet
, SideSheet
או בהתאמה אישית, צריך להוסיף את הרווח באמצעותViewCompat.setOnApplyWindowInsetsListener
. בשבילRecyclerView
, מחילים את המילוי באמצעות הבורר הזה ומוסיפים גם אתclipToPadding="false"
.
מה בודקים אם האפליקציה חייבת להציע הגנה מותאמת אישית לרקע
אם האפליקציה צריכה להציע הגנה מותאמת אישית ברקע לניווט ב-3 לחצנים או
לשורת הסטטוס, צריך להציב באפליקציה תוכן קומפוזבילי או תצוגה מאחורי סרגל המערכת באמצעות WindowInsets.Type#tappableElement()
כדי לקבל את הגובה של סרגל הניווט ב-3 לחצנים או את WindowInsets.Type#statusBars
.
משאבים נוספים מקצה לקצה
במאמרים תצוגות מקצה לקצה ועריכה מקצה לקצה מפורטות הנחיות נוספות לשימוש בפריימים חתוכים.
ממשקי API שהוצאו משימוש
ממשקי ה-API הבאים הוצאו משימוש, אבל לא הושבתו:
R.attr#enforceStatusBarContrast
R.attr#navigationBarColor
(לניווט ב-3 לחצנים, עם אלפא של 80%)Window#isStatusBarContrastEnforced
Window#setNavigationBarColor
(לניווט ב-3 לחצנים, עם 80% אלפא)Window#setStatusBarContrastEnforced
ממשקי ה-API הבאים הוצאו משימוש והושבתו:
R.attr#navigationBarColor
(לניווט באמצעות תנועות)R.attr#navigationBarDividerColor
R.attr#statusBarColor
Window#setDecorFitsSystemWindows
Window#getNavigationBarColor
Window#getNavigationBarDividerColor
Window#getStatusBarColor
Window#setNavigationBarColor
(לניווט באמצעות תנועות)Window#setNavigationBarDividerColor
Window#setStatusBarColor
הגדרה יציבה
אם האפליקציה שלכם מטרגטת ל-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
, שורת הסטטוס והסרגל העליון לא נכללים יותר. Configuration.smallestScreenWidthDp
מושפע באופן עקיף משינויים ב-screenWidthDp
וב-screenHeightDp
.Configuration.orientation
מושפע באופן עקיף משינויים ב-screenWidthDp
וב-screenHeightDp
במכשירים שצורתם קרובה למרובעת.Display.getSize(Point)
מושפע באופן עקיף מהשינויים ב-Configuration
. האפשרות הזו הוצאה משימוש החל מרמת API 30.Display.getMetrics()
כבר פועל כך מאז רמת API 33.
ערך ברירת המחדל של מאפיין maxTextHeight הוא 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
.
שינויים ברוחב של TextView עבור צורות אותיות מורכבות
בגרסאות קודמות של Android, חלק מגופנים או שפות עם כתב מחובר עם עיצוב מורכב עשויים לצייר את האותיות באזור של התו הקודם או הבא.
במקרים מסוימים, אותיות כאלה נחתכו בנקודת ההתחלה או הסיום.
החל מ-Android 15, TextView
מקצה רוחב לציור מספיק מקום לאותיות כאלה ומאפשר לאפליקציות לבקש תוספת רווח שמימין כדי למנוע חיתוך.
מכיוון שהשינוי הזה משפיע על האופן שבו TextView
מחליט על הרוחב, TextView
מקצה יותר רוחב כברירת מחדל אם האפליקציה מטרגטת ל-Android 15 (רמת API 35) ואילך. אפשר להפעיל או להשבית את ההתנהגות הזו על ידי שליחת קריאה ל-API setUseBoundsForWidth
ב-TextView
.
הוספת רווח פנימי בצד ימין עלולה לגרום לחוסר התאמה של פריסות קיימות, ולכן הוא לא מתווסף כברירת מחדל גם לאפליקציות שמטרגטות את Android מגרסה 15 ואילך.
עם זאת, אפשר להוסיף עוד ריפוד כדי למנוע חיתוך על ידי קריאה ל-setShiftDrawingOffsetForStartOverhang
.
הדוגמאות הבאות מראות איך השינויים האלה יכולים לשפר את פריסת הטקסט בגופנים ובשפות מסוימים.
גובה שורה שמוגדר כברירת מחדל בהתאם לאזור הגיאוגרפי של 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:
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:
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 ואילך.
הגבלות על בקשות להתמקד באודיו
Apps that target Android 15 (API level 35) must be the top app or running a
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
.
You can learn more about audio focus at Manage audio focus.
עדכון ההגבלות על רכיבים שאינם SDK
מערכת Android 15 כוללת רשימות מעודכנות של ממשקים מוגבלים שאינם ערכות SDK, על סמך שיתוף פעולה עם מפתחי Android והבדיקות הפנימיות האחרונות. כשהדבר אפשרי, אנחנו מוודאים שחלופות ציבוריות זמינות לפני שאנחנו מגבילים ממשקים שהם לא SDK.
אם האפליקציה שלכם לא מטרגטת את Android 15, יכול להיות שחלק מהשינויים האלה לא ישפיעו עליכם באופן מיידי. עם זאת, אמנם אפשר לתת לאפליקציה גישה לממשקים מסוימים שאינם SDK בהתאם לרמת ה-API לטירגוט של האפליקציה, אבל שימוש בשדה או בשיטה שלא שייכים ל-SDK תמיד כרוך בסיכון גבוה לשיבוש האפליקציה.
אם אתם לא בטוחים אם באפליקציה שלכם נעשה שימוש בממשקים שאינם SDK, תוכלו לבדוק את האפליקציה כדי לברר זאת. אם האפליקציה שלכם מסתמכת על ממשקים שאינם SDK, כדאי להתחיל לתכנן את המעבר לחלופות SDK. עם זאת, אנחנו מבינים שלאפליקציות מסוימות יש תרחישים לדוגמה שבהם יש צורך להשתמש בממשקים שאינם SDK. אם לא מצאתם חלופה לשימוש בממשק שאינו SDK עבור תכונה באפליקציה, עליכם לבקש ממשק API ציבורי חדש.
מידע נוסף על השינויים בגרסה הזו של Android זמין במאמר עדכונים לגבי הגבלות על ממשקים שאינם SDK ב-Android 15. מידע נוסף על ממשקים שאינם ב-SDK זמין במאמר הגבלות על ממשקים שאינם ב-SDK.