1. לפני שמתחילים
ה-Codelab הזה מראה איך ליצור פרופילים של Baseline כדי לבצע אופטימיזציה של הביצועים של האפליקציה, ואיך לוודא את היתרונות בביצועים של השימוש בפרופילים של Baseline.
מה תצטרכו
- Android Studio Hedgehog (2023.1.1) או גרסה חדשה יותר
- פלאגין של Android Gradle 8.0 ואילך
- הבנה בסיסית של Jetpack Macrobenchmark
- מכשיר Android פיזי עם Android 7 (רמת API 24) או גרסה מתקדמת יותר
הפעולות שתבצעו:
- מגדירים את הפרויקט לשימוש ברכיבים יוצרים של פרופילים של Baseline.
- ליצור פרופילים של Baseline כדי לשפר את זמן ההפעלה של האפליקציה ואת ביצועי הגלילה.
- כדי לאמת את שיפורי הביצועים, אפשר להשתמש בספריית Jetpack Macrobenchmark.
מה תלמדו
- פרופילים של Baseline והסבר על האופן שבו הם יכולים לשפר את ביצועי האפליקציה.
- איך יוצרים פרופילים של Baseline.
- שיפורים בביצועים של פרופילים של Baseline.
2. תהליך ההגדרה
כדי להתחיל, משכפלים את מאגר GitHub משורת הפקודה באמצעות הפקודה הבאה:
$ git clone https://github.com/android/codelab-android-performance.git
לחלופין, אפשר להוריד שני קובצי ZIP:
פתיחת פרויקט ב-Android Studio
- בחלון 'ברוכים הבאים ל-Android Studio', בוחרים באפשרות
פתיחת פרויקט קיים. - בוחרים את התיקייה
[Download Location]/codelab-android-performance/baseline-profiles. חשוב לוודא שבוחרים בספרייהbaseline-profiles. - כשמייבאים את הפרויקט ל-Android Studio, צריך לוודא שאפשר להריץ את המודול
appכדי ליצור את האפליקציה לדוגמה שבה תעבדו בהמשך.
האפליקציה לדוגמה
ב-Codelab הזה עובדים עם אפליקציית JetSnack לדוגמה. זו אפליקציה וירטואלית להזמנת חטיפים שמשתמשת ב-Jetpack פיתוח נייטיב.
כדי למדוד את הביצועים של האפליקציה, צריך להבין את מבנה ממשק המשתמש ואת אופן הפעולה של האפליקציה, כדי שתוכלו לגשת לרכיבי ממשק המשתמש מהמדדים. מריצים את האפליקציה ומזמינים חטיפים כדי להכיר את המסכים הבסיסיים. אתם לא צריכים לדעת את הפרטים של הארכיטקטורה של האפליקציה.

3. מהם פרופילים של Baseline
פרופילים של Baseline משפרים את מהירות הרצת הקוד ב-30% בערך מההפעלה הראשונה, כי הם מאפשרים להימנע משלבי פרשנות וקימפול just-in-time (JIT) לנתיבי הקוד הכלולים. כשמצרפים פרופיל Baseline לאפליקציה או לספרייה, סביבת זמן הריצה ל-Android (ART) יכולה לבצע אופטימיזציה של נתיבי קוד ספציפיים באמצעות קימפול מראש (AOT), וכך לשפר את הביצועים לכל משתמש חדש ולכל עדכון לאפליקציה. זו אופטימיזציה מונחית פרופיל (PGO) שמאפשרת לאפליקציות להעלות את איכות ההפעלה, לצמצם בעיות בממשק (jank) שקשורות לאינטראקציה, ולשפר את הביצועים הכוללים של זמן הריצה למשתמשי הקצה כבר מההפעלה הראשונה.
עם פרופיל Baseline, כל האינטראקציות של המשתמשים – כמו הפעלת האפליקציה, מעבר בין מסכים או גלילה בתוכן – חלקות יותר כבר מהפעם הראשונה שהם מפעילים אותה. הגברת המהירות ויכולת התגובה של האפליקציה מובילה ליותר משתמשים פעילים ביום (DAU) ולשיעור גבוה יותר של משתמשים שחוזרים לאפליקציה.
פרופילים של Baseline עוזרים לבצע אופטימיזציה מעבר להפעלה של האפליקציה. הם מאפשרים למשתמשים לבצע פעולות נפוצות שמשפרות את זמן הריצה של האפליקציה החל מההפעלה הראשונה. הקימפול המודרך מסוג AOT לא מסתמך על המכשירים של המשתמשים. אפשר לבצע את השלב הזה פעם אחת לכל גרסה במחשב פיתוח במקום במכשיר נייד. כשמשתמשים בפרופיל Baseline כדי לשלוח גרסאות, האופטימיזציות של האפליקציה זמינות הרבה יותר מהר מאשר כשמסתמכים רק על פרופילים של Cloud.
כשלא משתמשים בפרופיל Baseline, כל קוד האפליקציה עובר הידור JIT בזיכרון אחרי שהוא מפוענח, או שהוא עובר הידור לקובץ odex ברקע כשהמכשיר בלי פעילות. לכן, יכול להיות שחוויית השימוש של המשתמשים לא תהיה אופטימלית כשהם יפעילו אפליקציה אחרי שהתקינו או עדכנו אותה בפעם הראשונה, לפני שנתיבי הקוד החדשים יעברו אופטימיזציה.
4. הגדרת המודול ליצירת פרופיל Baseline
אפשר ליצור פרופילים של Baseline באמצעות מחלקה של בדיקת אינסטרומנטציה שדורשת הוספה של מודול Gradle חדש לפרויקט. הדרך הכי קלה להוסיף אותו לפרויקט היא באמצעות אשף המודולים של Android Studio שמגיע עם Android Studio Hedgehog או גרסה מתקדמת יותר.
פותחים את חלון האשף של המודול החדש. לשם כך, לוחצים לחיצה ימנית על הפרויקט או המודול בחלונית Project ובוחרים באפשרות New > Module.

בחלונית Templates (תבניות) בחלון שנפתח, בוחרים באפשרות Baseline Profile Generator (מחולל פרופיל Baseline).

בנוסף לפרמטרים הרגילים כמו שם המודול, שם החבילה, השפה או שפת תצורת build, יש שני קלטים שלא רגילים למודול חדש: אפליקציית היעד ושימוש במכשיר בניהול Gradle.
אפליקציית היעד היא מודול האפליקציה שמשמש ליצירת פרופילי Baseline. אם יש לכם יותר ממודול אפליקציה אחד בפרויקט, בוחרים את המודול שרוצים להריץ עבורו את הגנרטורים.
תיבת הסימון Use Gradle Managed Device (שימוש במכשיר בניהול Gradle) מגדירה את המודול להפעלה של רכיבי היוצר של פרופיל ה-Baseline באמולטורים של Android שמנוהלים באופן אוטומטי. מידע נוסף על מכשירים בניהול Gradle זמין במאמר הרחבת הבדיקות באמצעות מכשירים בניהול Gradle. אם מבטלים את הסימון של התיבה הזו, הגנרטורים משתמשים בכל מכשיר מחובר.
אחרי שמגדירים את כל הפרטים של המודול החדש, לוחצים על סיום כדי להמשיך בתהליך יצירת המודול.
שינויים שבוצעו על ידי אשף המודולים
אשף המודולים מבצע כמה שינויים בפרויקט.
נוסף מודול Gradle בשם baselineprofile או בשם שבחרתם באשף.
המודול הזה משתמש בתוסף com.android.test, שאומר ל-Gradle לא לכלול אותו באפליקציה, כך שהוא יכול להכיל רק קוד בדיקה או מדדים. הוא גם מחיל את התוסף androidx.baselineprofile, שמאפשר ליצור פרופילי Baseline באופן אוטומטי.
אשף ההגדרות גם מבצע שינויים במודול של אפליקציית היעד שבוחרים. הוא מפעיל את הפלאגין androidx.baselineprofile, מוסיף את התלות androidx.profileinstaller ומוסיף את התלות baselineProfile למודול build.gradle(.kts) שנוצר:
plugins {
id("androidx.baselineprofile")
}
dependencies {
// ...
implementation("androidx.profileinstaller:profileinstaller:1.3.0")
"baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}
הוספת יחסי התלות androidx.profileinstaller מאפשרת לכם:
- בודקים באופן מקומי את שיפורי הביצועים של פרופילי ה-Baseline שנוצרו.
- משתמשים בפרופילים של Baseline ב-Android 7 (רמת API 24) וב-Android 8 (רמת API 26), שלא תומכים בפרופילים של Cloud.
- שימוש בפרופילים של Baseline במכשירים שאין בהם Google Play Services.
התלות baselineProfile(project(":baselineprofile")) מאפשרת ל-Gradle לדעת מאיזה מודול צריך לקחת את פרופילי ה-Baseline שנוצרו.
אחרי שמגדירים את הפרויקט, כותבים מחלקה ליצירת פרופילים של Baseline.
5. כתיבת רכיב ליצירת פרופיל Baseline
בדרך כלל, יוצרים פרופילים של Baseline לתהליכים טיפוסיים שעוברים המשתמשים באפליקציה.
אשף המודולים יוצר מחלקת בדיקה בסיסית BaselineProfileGenerator שיכולה ליצור את פרופיל ה-Baseline להפעלת האפליקציה, והיא נראית כך:
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect("com.example.baselineprofiles_codelab") {
// This block defines the app's critical user journey. This is where you
// optimize for app startup. You can also navigate and scroll
// through your most important UI.
// Start default activity for your app.
pressHome()
startActivityAndWait()
// TODO Write more interactions to optimize advanced journeys of your app.
// For example:
// 1. Wait until the content is asynchronously loaded.
// 2. Scroll the feed content.
// 3. Navigate to detail screen.
// Check UiAutomator documentation for more information about how to interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
}
}
במחלקת הבדיקה הזו נעשה שימוש בBaselineProfileRule כלל בדיקה, והיא מכילה שיטת בדיקה אחת ליצירת הפרופיל. נקודת הכניסה ליצירת הפרופיל היא הפונקציה collect(). הוא כולל רק שני פרמטרים:
-
packageName: החבילה של האפליקציה. -
profileBlock: הפרמטר האחרון של lambda.
ב-profileBlock lambda, מציינים את האינטראקציות שמכסות את מסלולי המשתמשים האופייניים באפליקציה. הספרייה מפעילה את profileBlock כמה פעמים, אוספת את המחלקות והפונקציות שנקראו ומפיקה את פרופיל ה-Baseline במכשיר עם הקוד שיש לבצע בו אופטימיזציה.
כברירת מחדל, מחלקת הגנרטור שנוצרת מכילה אינטראקציות להפעלת Activity שמוגדר כברירת מחדל, וממתינה עד שהפריים הראשון של האפליקציה יעבור רינדור באמצעות השיטה startActivityAndWait().
הרחבת הגנרטור באמצעות תהליכים מותאמים אישית
אפשר לראות שהקלאס שנוצר כולל גם כמה TODO כדי לכתוב עוד אינטראקציות ולבצע אופטימיזציה של מסלולים מתקדמים באפליקציה. מומלץ לעשות את זה כדי לשפר את הביצועים מעבר להפעלה של האפליקציה.
באפליקציה לדוגמה שלנו, אפשר לזהות את המסלולים האלה באופן הבא:
- מפעילים את האפליקציה. הנושא הזה כבר מכוסה באופן חלקי בכיתה שנוצרה.
- מחכים עד שהתוכן ייטען באופן אסינכרוני.
- גוללים ברשימת החטיפים.
- עוברים לפרטי החטיף.
משנים את הגנרטור כך שיכיל את הפונקציות המפורטות שמתייחסות לתהליכים האופייניים בקטע הקוד הבא:
// ...
rule.collect("com.example.baselineprofiles_codelab") {
// This block defines the app's critical user journey. This is where you
// optimize for app startup. You can also navigate and scroll
// through your most important UI.
// Start default activity for your app.
pressHome()
startActivityAndWait()
// TODO Write more interactions to optimize advanced journeys of your app.
// For example:
// 1. Wait until the content is asynchronously loaded.
waitForAsyncContent()
// 2. Scroll the feed content.
scrollSnackListJourney()
// 3. Navigate to detail screen.
goToSnackDetailJourney()
// Check UiAutomator documentation for more information about how to interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
// ...
עכשיו תכתוב אינטראקציות לכל אחד מהמסלולים שציינת. אפשר לכתוב אותה כפונקציית ההרחבה של MacrobenchmarkScope כדי לקבל גישה לפרמטרים ולפונקציות שהיא מספקת. הכתיבה בצורה הזו מאפשרת לכם לעשות שימוש חוזר באינטראקציות עם נקודות ההשוואה כדי לאמת את שיפורי הביצועים.
המתנה לתוכן אסינכרוני
באפליקציות רבות יש טעינה אסינכרונית כלשהי בהפעלת האפליקציה, שנקראת גם מצב תצוגה מלא. המצב הזה מציין למערכת מתי התוכן נטען ועבר עיבוד, והמשתמש יכול ליצור איתו אינטראקציה. מחכים למצב במחולל (waitForAsyncContent) עם האינטראקציות האלה:
- תחפש את רשימת החטיפים בפיד.
- מחכים עד שחלק מהפריטים ברשימה יהיו גלויים במסך.
fun MacrobenchmarkScope.waitForAsyncContent() {
device.wait(Until.hasObject(By.res("snack_list")), 5_000)
val contentList = device.findObject(By.res("snack_list"))
// Wait until a snack collection item within the list is rendered.
contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}
תהליך הגלילה ברשימה
במסלול של רשימת החטיפים עם הגלילה (scrollSnackListJourney), אפשר לבצע את האינטראקציות הבאות:
- מחפשים את רכיב ממשק המשתמש של רשימת החטיפים.
- מגדירים את שולי התנועה כך שהניווט במערכת לא יופעל.
- גוללים ברשימה ומחכים עד שממשק המשתמש מתייצב.
fun MacrobenchmarkScope.scrollSnackListJourney() {
val snackList = device.findObject(By.res("snack_list"))
// Set gesture margin to avoid triggering gesture navigation.
snackList.setGestureMargin(device.displayWidth / 5)
snackList.fling(Direction.DOWN)
device.waitForIdle()
}
מעבר לפרטי המסלול להמרה
המסע האחרון (goToSnackDetailJourney) כולל את האינטראקציות הבאות:
- תמצא את רשימת החטיפים ואת כל החטיפים שאפשר לעבוד איתם.
- בוחרים פריט מהרשימה.
- לוחצים על הפריט ומחכים עד שדף הפרטים ייטען. אפשר לנצל את העובדה שרשימת החטיפים לא תופיע יותר על המסך.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
val snackList = device.findObject(By.res("snack_list"))
val snacks = snackList.findObjects(By.res("snack_item"))
// Select snack from the list based on running iteration.
val index = (iteration ?: 0) % snacks.size
snacks[index].click()
// Wait until the screen is gone = the detail is shown.
device.wait(Until.gone(By.res("snack_list")), 5_000)
}
אחרי שמגדירים את כל האינטראקציות שדרושות כדי שהרכיב היוצר של פרופיל ה-Baseline יהיה מוכן להרצה, צריך להגדיר את המכשיר שבו הוא יפעל.
6. הכנת מכשיר להרצת הגנרטור
כדי ליצור פרופילי Baseline, מומלץ להשתמש באמולטור כמו מכשיר בניהול Gradle או במכשיר עם Android 13 (API 33) ומעלה.
כדי שהתהליך יהיה ניתן לשחזור וכדי ליצור פרופילים של Baseline באופן אוטומטי, אפשר להשתמש במכשירים בניהול Gradle. מכשירים בניהול Gradle מאפשרים להריץ בדיקות באמולטור של Android בלי להפעיל ולסגור אותו באופן ידני. מידע נוסף על מכשירים בניהול Gradle זמין במאמר הרחבת הבדיקות עם מכשירים בניהול Gradle.
כדי להגדיר מכשיר מנוהל ב-Gradle, מוסיפים את ההגדרה שלו לקובץ :baselineprofile של מודול build.gradle.kts, כמו שמוצג בקטע הקוד הבא:
android {
// ...
testOptions.managedDevices.devices {
create<ManagedVirtualDevice>("pixel6Api31") {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
}
}
}
במקרה הזה, אנחנו משתמשים ב-Android 11 (רמת API 31) וaosp קובץ אימג' של המערכת מאפשר גישת root.
לאחר מכן, מגדירים את הפלאגין Baseline Profile Gradle לשימוש במכשיר בניהול Gradle שהוגדר. כדי לעשות זאת, מוסיפים את שם המכשיר לנכס managedDevices ומשביתים את useConnectedDevices, כפי שמוצג בקטע הקוד הבא:
android {
// ...
}
baselineProfile {
managedDevices += "pixel6Api31"
useConnectedDevices = false
}
dependencies {
// ...
}
לאחר מכן, יוצרים את פרופיל ה-Baseline.
7. יצירת פרופיל ה-Baseline
כשהמכשיר מוכן, אפשר ליצור את פרופיל ה-Baseline. הפלאגין Baseline Profile Gradle יוצר משימות Gradle כדי להפוך את כל התהליך של הפעלת מחלקת הבדיקה של הגנרטור והחלת פרופילי ה-Baseline שנוצרו באפליקציה לאוטומטי.
אשף המודולים החדש יצר הגדרת הפעלה כדי שתוכלו להריץ במהירות את משימת Gradle עם כל הפרמטרים הנדרשים להפעלה, בלי שתצטרכו לעבור בין הטרמינל ל-Android Studio
כדי להריץ אותו, מאתרים את הגדרת ההרצה Generate Baseline Profile ולוחצים על לחצן ההרצה
.

המשימה מפעילה את תמונת האמולטור שהוגדרה קודם. מריצים את האינטראקציות מתוך מחלקת הבדיקה BaselineProfileGenerator כמה פעמים, ואז מפרקים את האמולטור ומספקים את הפלט ל-Android Studio.
אחרי שהגנרטור מסיים את הפעולה בהצלחה, פלאגין Gradle מכניס אוטומטית את baseline-prof.txt שנוצר לאפליקציית היעד (מודול :app) בתיקייה src/release/generated/baselineProfile/.

(אופציונלי) הפעלת הגנרטור משורת הפקודה
אפשר גם להריץ את הגנרטור משורת הפקודה. אפשר להשתמש במשימה שנוצרה על ידי המכשיר בניהול Gradle – :app:generateBaselineProfile. הפקודה הזו מריצה את כל הבדיקות בפרויקט שהוגדר על ידי התלות baselineProfile(project(:baselineProfile)). המודול מכיל גם מדדי השוואה לצורך אימות מאוחר יותר של שיפורי הביצועים, ולכן הבדיקות האלה נכשלות עם אזהרה מפני הפעלת מדדי השוואה באמולטור.
android .testInstrumentationRunnerArguments .androidx.benchmark.enabledRules=BaselineProfile
לשם כך, אפשר לסנן את כל הרכיבים היוצרים של פרופילים של Baseline באמצעות הארגומנט הבא של כלי ההרצה של האינסטרומנטציה, וכל המדדים להשוואה ידלגו:
הפקודה המלאה נראית כך:
./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
הפצת האפליקציה באמצעות פרופילים של Baseline
אחרי שפרופיל ה-Baseline נוצר והועתק לקוד המקור של האפליקציה, צריך ליצור את גרסת הייצור של האפליקציה כמו שאתם עושים בדרך כלל. לא צריך לעשות שום דבר נוסף כדי להפיץ את פרופילי ה-Baseline למשתמשים. הם נבחרים על ידי פלאגין של Android Gradle במהלך ה-build ונכללים ב-AAB או ב-APK. לאחר מכן מעלים את הגרסה ל-Google Play.
כשמשתמשים מתקינים את האפליקציה או מעדכנים אותה מהגרסה הקודמת, פרופיל ה-Baseline מותקן גם הוא, וכך הביצועים משתפרים כבר מההפעלה הראשונה של האפליקציה.
בשלב הבא מוסבר איך בודקים את שיפור ביצועי האפליקציה באמצעות פרופילים של Baseline.
8. (אופציונלי) התאמה אישית של יצירת פרופילים של Baseline
הפלאגין Baseline Profiles Gradle כולל אפשרויות להתאמה אישית של אופן יצירת הפרופילים, כדי שיתאימו לצרכים הספציפיים שלכם. אפשר לשנות את ההתנהגות באמצעות בלוק ההגדרה baselineProfile { } בסקריפטים של build.
בלוק ההגדרה במודול :baselineprofile משפיע על אופן ההרצה של הגנרטורים, עם אפשרות להוסיף managedDevices ולהחליט אם להשתמש ב-useConnectedDevices או במכשירים בניהול Gradle.
בלוק ההגדרה במודול היעד :app קובע איפה הפרופילים נשמרים או איך הם נוצרים. אפשר לשנות את הפרמטרים הבאים:
-
automaticGenerationDuringBuild: אם האפשרות הזו מופעלת, אפשר ליצור את פרופיל ה-Baseline כשיוצרים את גרסת ה-build להפצה. האפשרות הזו מועילה כשמבצעים בנייה ב-CI לפני ששולחים את האפליקציה. -
saveInSrc: מציין אם פרופיל ה-Baseline שנוצר מאוחסן בתיקייהsrc/. אפשר גם לגשת לקובץ מתיקיית ה-build:baselineprofile. -
baselineProfileOutputDir: מגדיר איפה לאחסן את פרופילי ה-Baseline שנוצרו. mergeIntoMain: כברירת מחדל, פרופילים של Baseline נוצרים לכל וריאנט build (גרסת המוצר וסוג build). אם רוצים למזג את כל הפרופילים לתוךsrc/main, אפשר להפעיל את הדגל הזה.-
filter: אפשר לסנן אילו מחלקות או שיטות לכלול או להחריג מפרופילים של Baseline שנוצרו. האפשרות הזו יכולה להיות שימושית למפתחי ספריות שרוצים לכלול רק את הקוד מהספרייה.
9. אימות שיפורים בביצועים של ההפעלה
אחרי שיוצרים את פרופיל Baseline ומוסיפים אותו לאפליקציה, צריך לוודא שהוא משפיע על ביצועי האפליקציה כמו שרוצים.
אשף המודול החדש יוצר מחלקה להשוואה לשוק בשם StartupBenchmarks. הוא מכיל השוואה למדידת זמן ההפעלה של האפליקציה, ומשווה אותו למצב שבו האפליקציה משתמשת בפרופילים של Baseline.
הכיתה נראית כך:
@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())
@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
private fun benchmark(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = "com.example.baselineprofiles_codelab",
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
// TODO Add interactions to wait for when your app is fully drawn.
// The app is fully drawn when Activity.reportFullyDrawn is called.
// For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
// from the AndroidX Activity library.
// Check the UiAutomator documentation for more information on how to
// interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
)
}
}
הוא משתמש ב-MacrobenchmarkRule שיכול להריץ בדיקות השוואה לאפליקציה שלכם ולאסוף מדדי ביצועים. נקודת הכניסה לכתיבת מדד השוואה היא הפונקציה measureRepeated מהכלל.
היא דורשת כמה פרמטרים:
packageName:איזו אפליקציה למדוד.-
metrics: איזה סוג מידע רוצים למדוד במהלך ההשוואה לשוק. iterations: מספר הפעמים שנקודת ההשוואה חוזרת על עצמה.-
startupMode: איך רוצים שהאפליקציה תתחיל עם תחילת ההשוואה לשוק. -
setupBlock: אילו אינטראקציות עם האפליקציה צריכות לקרות לפני המדידה. -
measureBlock: אינטראקציות עם האפליקציה שרוצים למדוד במהלך ההשוואה לשוק.
בנוסף, מחלקת הבדיקה מכילה שתי בדיקות: startupCompilationeNone() ו-startupCompilationBaselineProfiles(), שקוראות לפונקציה benchmark() עם ערכים שונים של compilationMode.
CompilationMode
הפרמטר CompilationMode מגדיר איך האפליקציה עוברת קומפילציה מראש לשפת מכונה. יש לו את האפשרויות הבאות:
-
DEFAULT: קומפילציה מראש חלקית של האפליקציה באמצעות פרופילי בסיס, אם הם זמינים. הפרמטר הזה משמש אם לא מוחל פרמטרcompilationMode. -
None(): מאפס את מצב ההידור של האפליקציה ולא מבצע הידור מראש של האפליקציה. הידור בזמן אמת (JIT) עדיין מופעל במהלך ההרצה של האפליקציה. -
Partial(): מבצע קומפילציה מראש של האפליקציה באמצעות פרופילי בסיס או הרצות חימום, או שניהם. -
Full(): מבצע קומפילציה מראש של כל קוד האפליקציה. זו האפשרות היחידה ב-Android 6 (API 23) ובגרסאות קודמות.
אם רוצים להתחיל באופטימיזציה של ביצועי האפליקציה, אפשר לבחור במצב DEFAULT קומפילציה, כי הביצועים דומים לביצועים של האפליקציה כשהיא מותקנת מ-Google Play. כדי להשוות את היתרונות בביצועים שמספקים פרופילים של Baseline, אפשר להשוות את התוצאות של מצב הידור None ושל Partial.
שינוי נקודת ההשוואה כך שתמתין לתוכן
המדדים נכתבים באופן דומה ליוצרי פרופילים של Baseline, על ידי כתיבת אינטראקציות עם האפליקציה. כברירת מחדל, המדדים שנוצרו ממתינים רק עד שהפריים הראשון יעבור רינדור – בדומה למה שקורה ב-BaselineProfileGenerator – ולכן מומלץ לשפר את המדדים כך שימתינו לתוכן האסינכרוני.
אפשר לעשות את זה על ידי שימוש חוזר בפונקציות של התוסף שכתבתם עבור הגנרטור. המדד הזה מתעד את זמני ההפעלה – באמצעות StartupTimingMetric() – ולכן מומלץ לכלול כאן רק את ההמתנה לתוכן האסינכרוני, ואז לכתוב מדד נפרד לתרחישי השימוש האחרים שמוגדרים בגנרטור.
// ...
measureBlock = {
startActivityAndWait()
// The app is fully drawn when Activity.reportFullyDrawn is called.
// For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
// from the AndroidX Activity library.
waitForAsyncContent() // <------- Added to wait for async content.
// Check the UiAutomator documentation for more information on how to
// interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
הפעלת ההשוואה לשוק
אפשר להריץ את בדיקות ההשוואה באותו אופן שבו מריצים בדיקות עם מכשור. אפשר להריץ את פונקציית הבדיקה או את כל המחלקה באמצעות הסמל שבשוליים לצד הפונקציה או המחלקה.

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

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

מצילום המסך אפשר לראות שזמן ההפעלה של האפליקציה שונה בכל CompilationMode. הערכים החציוניים מוצגים בטבלה הבאה:
timeToInitialDisplay [אלפיות השנייה] | timeToFullDisplay [אלפיות השנייה] | |
ללא | 202.2 | 818.8 |
BaselineProfiles | 193.7 | 637.9 |
שיפור | 4% | 28% |
ההפרש בין מצבי הקומפילציה של timeToFullDisplay הוא 180ms,שזה שיפור של כ-28% רק בזכות פרופיל Baseline. הביצועים של CompilationNone גרועים יותר, כי המכשיר צריך לבצע את רוב ההידור JIT במהלך ההפעלה של האפליקציה. הביצועים של CompilationBaselineProfiles טובים יותר, כי הידור חלקי באמצעות פרופילים של Baseline מבצע הידור AOT של הקוד שהמשתמש צפוי להשתמש בו, ומשאיר את הקוד הלא קריטי ללא הידור מראש, כך שהוא לא צריך להיטען באופן מיידי.
10. (אופציונלי) אימות שיפור ביצועי הגלילה
בדומה לשלב הקודם, אפשר למדוד ולאמת את ביצועי הגלילה. קודם יוצרים מחלקת בדיקה ScrollBenchmarks עם כלל ההשוואה ושתי שיטות בדיקה שמשתמשות במצבי קומפילציה שונים:
@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {
@get:Rule
val rule = MacrobenchmarkRule()
@Test
fun scrollCompilationNone() = scroll(CompilationMode.None())
@Test
fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())
private fun scroll(compilationMode: CompilationMode) {
// TODO implement
}
}
בתוך השיטה scroll, משתמשים בפונקציה measureRepeated עם הפרמטרים הנדרשים. לפרמטר metrics, משתמשים בערך FrameTimingMetric, שמודד כמה זמן לוקח ליצור פריימים של ממשק משתמש:
private fun scroll(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = "com.example.baselineprofiles_codelab",
metrics = listOf(FrameTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.WARM,
iterations = 10,
setupBlock = {
// TODO implement
},
measureBlock = {
// TODO implement
}
)
}
במקרה הזה, צריך לפצל את האינטראקציות יותר בין setupBlock ל-measureBlock כדי למדוד רק את משכי הזמן של הפריים במהלך הפריסה הראשונה והגלילה של התוכן. לכן, צריך להוסיף את הפונקציות שמתחילות את המסך שמוגדר כברירת מחדל ל-setupBlock, ואת הפונקציות של התוסף שכבר נוצרו waitForAsyncContent() ו-scrollSnackListJourney() ל-measureBlock:
private fun scroll(compilationMode: CompilationMode) {
rule.measureRepeated(
packageName = "com.example.baselineprofiles_codelab",
metrics = listOf(FrameTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.WARM,
iterations = 10,
setupBlock = {
pressHome()
startActivityAndWait()
},
measureBlock = {
waitForAsyncContent()
scrollSnackListJourney()
}
)
}
אחרי שההשוואה מוכנה, אפשר להריץ אותה כמו קודם כדי לקבל תוצאות כמו בצילום המסך הבא:

הפלט של FrameTimingMetric הוא משך הפריימים באלפיות השנייה (frameDurationCpuMs) במאון ה-50, ה-90, ה-95 וה-99. ב-Android 12 (רמת API 31) ובגרסאות מתקדמות יותר, הפונקציה מחזירה גם את משך הזמן שבו הפריימים חרגו מהמגבלה (frameOverrunMs). הערך יכול להיות שלילי, מה שאומר שנשאר זמן נוסף ליצירת הפריים.
מתוצאות הבדיקה אפשר לראות שהמכשיר CompilationBaselineProfiles מציג משך פריימים קצר יותר ב-2ms בממוצע, וזה לא בהכרח משהו שהמשתמשים ישימו לב אליו. עם זאת, באחוזונים האחרים התוצאות ברורות יותר. ב-P99, ההבדל הוא 43.5ms, שזה יותר מ-3 פריימים שדילגו במכשיר שפועל ב-90 FPS. לדוגמה, ב-Pixel 6, 1,000ms / 90 FPS = ~11ms הוא הזמן המקסימלי לעיבוד פריים.
11. מזל טוב
כל הכבוד, סיימתם את ה-Codelab הזה ושיפרתם את הביצועים של האפליקציה באמצעות פרופילים של Baseline.
משאבים נוספים
כדאי לעיין במשאבים הנוספים הבאים:
- בדיקת ביצועי האפליקציה באמצעות Macrobenchmark: Codelab שמתעמק יותר בהשוואה לשוק.
- דוגמאות לביצועים: מאגר שמכיל דוגמאות לביצועים של Macrobenchmark ושל בדיקות אחרות.
- אפליקציית הדוגמה Now In Android: אפליקציה מהעולם האמיתי שמשתמשת בהשוואה לביצועים ובפרופילי Baseline כדי לשפר את הביצועים.