זמן ההפעלה של האפליקציה

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

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

הסבר על מצבי ההפעלה השונים של האפליקציה

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

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

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

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

הפעלה במצב התחלתי (cold start)

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

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

בתחילת ההפעלה במצב התחלתי (cold start), המערכת מבצעת את שלוש המשימות הבאות:

  1. טוענים ומפעילים את האפליקציה.
  2. להציג חלון התחלתי ריק של האפליקציה מיד אחרי ההפעלה.
  3. יוצרים את התהליך של האפליקציה.

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

  1. יוצרים את אובייקט האפליקציה.
  2. מפעילים את ה-thread הראשי.
  3. יוצרים את הפעילות הראשית.
  4. מאתחלים את ממשק המשתמש.
  5. מציירים את ממשק המשתמש על המסך.

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

מידע נוסף על שלבי הפריסה ב-Compose זמין במאמר שלבים ב-Jetpack Compose.

בעיות בביצועים יכולות להתרחש במהלך יצירת האפליקציה ויצירת פעילות המארח.

יצירת אפליקציה

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

אם מחליפים את Application.onCreate באפליקציה שלכם, המערכת מפעילה את השיטה onCreate באובייקט האפליקציה. לאחר מכן, האפליקציה יוצרת את שרשור ה-UI הראשי, שנקרא גם UI thread, ומטילה עליו את המשימה של יצירת פעילות המארח של האפליקציה.

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

יצירת פעילות

אחרי שתהליך האפליקציה יוצר את הפעילות, הפעילות מבצעת את הפעולות הבאות:

  1. מאפס את הערכים.
  2. קורא ליצרנים.
  3. מפעיל את שיטת הקריאה החוזרת, כמו Activity.onCreate, שמתאימה למצב הנוכחי במחזור החיים של הפעילות.

בדרך כלל, לשיטה onCreate יש את ההשפעה הגדולה ביותר על זמן הטעינה. באפליקציית Jetpack Compose, התקורה של השיטה onCreate נובעת בדרך כלל מהקומפוזיציה הראשונית. זה קורה כשהאפליקציה קוראת ל-setContent ומפעילה את רכיבי ה-Composable ברמה העליונה. היררכיה עמוקה או מורכבת של ממשק המשתמש, או פונקציות Composable שמבצעות חישובים כבדים ב-thread הראשי, יכולות להאריך את זמן הקומפוזיציה.

הפעלה במצב ביניים (Warm start)

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

  • המשתמש יוצא מהאפליקציה ואז מפעיל אותה מחדש. יכול להיות שהתהליך ימשיך לפעול, אבל האפליקציה תצטרך ליצור מחדש את הפעילות מאפס באמצעות קריאה ל-onCreate.

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

הפעלה מתוך הזיכרון (Hot start)

התקורה של הפעלה מתוך הזיכרון (Hot Start) של האפליקציה נמוכה יותר מזו של הפעלה במצב התחלתי (Cold Start). בהפעלה מתוך הזיכרון (Hot start), המערכת מעבירה את פעילות המארח של האפליקציה לחזית. אם כל ממשק המשתמש של האפליקציה עדיין נמצא בזיכרון, האפליקציה יכולה להימנע מחזרה על אתחול האובייקט, אתחול ממשק המשתמש ועיבוד.

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

הפעלה מתוך הזיכרון (Hot start) מציגה את אותו אופן פעולה במסך כמו תרחיש של הפעלה במצב התחלתי (cold start). תהליך המערכת מציג מסך ריק עד שהאפליקציה מסיימת לעבד את הפעילות.

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

איך מזהים את הפעלת האפליקציה ב-Perfetto

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

  1. ב-Perfetto, מוצאים את השורה עם מדד הנגזר Android App Startups. אם לא רואים את האפשרות הזו, מנסים ללכוד נתונים באמצעות אפליקציית מעקב המערכת במכשיר.

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

  3. כדי להצמיד את השורה Android App Startups (הפעלות של אפליקציות ל-Android), לוחצים על סמל ההצמדה שמופיע כשמעבירים את מצביע העכבר מעל השורה.

  4. גוללים לשורה עם האפליקציה הרלוונטית ולוחצים על התא הראשון כדי להרחיב את השורה.

  5. מקישים על w כדי להתקרב לשרשור הראשי, בדרך כלל בחלק העליון (מקישים על s, a, d כדי להתרחק, לזוז ימינה ולזוז שמאלה, בהתאמה).

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

כשמנתחים אפליקציית Jetpack Compose, אפשר להשתמש במעקב אחר קומפוזיציה כדי לראות פרטים מדויקים על הביצועים של ממשק המשתמש. חשוב לשים לב במיוחד לקטעי ה-trace בשרשור הראשי שמתחילים ב-Choreographer#doFrame. מחפשים פלחים כמו Compose:recompose,‏ Compose:layout ו-Compose:draw. הם מראים כמה זמן האפליקציה משקיעה בהרכבה, במדידה, במיקום ובציור של רכיבים ספציפיים.

שימוש במדדים כדי לבדוק ולשפר את זמני האתחול

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

היתרונות של שימוש במדדים של חברות סטארט-אפ

‫Android משתמש במדדים הזמן עד להצגה הראשונית (TTID) והזמן עד להצגה מלאה (TTFD) כדי לבצע אופטימיזציה של הפעלת אפליקציות במצב קר וחם. סביבת זמן הריצה ל-Android ‏ (ART) משתמשת בנתונים מהמדדים האלה כדי לבצע קומפילציה מראש של קוד בצורה יעילה, לצורך אופטימיזציה של הפעלות עתידיות.

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

הזמן עד להצגה הראשונית

הזמן להצגה ראשונית (TTID) הוא הזמן שחולף עד להצגת הפריימים הראשונים של ממשק המשתמש של האפליקציה. המדד הזה מודד את הזמן שנדרש לאפליקציה ליצור את הפריים הראשון שלה, כולל אתחול התהליך במהלך הפעלה מההתחלה (cold start), יצירת פעילות במהלך הפעלה מההתחלה או הפעלה מהזיכרון (warm start), והצגת הפריים הראשון. שמירה על זמן נמוך להצגת מודעה (TTID) באפליקציה עוזרת לשפר את חוויית המשתמש, כי המשתמשים יכולים לראות את האפליקציה נפתחת במהירות. ה-TTID מדווח באופן אוטומטי לכל אפליקציה על ידי Android Framework. כשמבצעים אופטימיזציה של הפעלת האפליקציה, מומלץ להטמיע את התג reportFullyDrawn כדי לקבל מידע עד TTFD.

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

  • התהליך מתחיל.
  • מתבצע אתחול של האובייקטים.
  • יצירה ואתחול של פעילות המארח.
  • אתחול ממשק המשתמש.
  • הצגת האפליקציה בפעם הראשונה.

אחזור TTID

כדי למצוא את ה-TTID, מחפשים בכלי Logcat בשורת הפקודה שורת פלט שמכילה ערך בשם Displayed. הערך הזה הוא TTID והוא נראה כמו בדוגמה הבאה, שבה TTID הוא 3s534ms:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

כדי למצוא את TTID ב-Android Studio, משביתים את המסננים בתצוגת Logcat מהתפריט הנפתח של המסננים, ואז מוצאים את השעה Displayed, כמו שמוצג באיור 4. השבתת המסננים נדרשת כי השרת של המערכת, ולא האפליקציה עצמה, מציג את היומן הזה.

איור 4. המסננים מושבתים והערך Displayed מופיע ב-Logcat.

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

לפעמים השורה Displayed בפלט של Logcat מכילה שדה נוסף של זמן כולל, כמו בדוגמה הבאה:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

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

מומלץ להשתמש ב-Logcat ב-Android Studio, אבל אם אתם לא משתמשים ב-Android Studio, אתם יכולים גם למדוד את זמן ההמתנה עד להצגת המודעה על ידי הפעלת האפליקציה עם הפקודה adb shell activity manager. הנה דוגמה:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

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

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

הארגומנטים -c ו--a הם אופציונליים ומאפשרים לציין את <category> ואת <action>.

הזמן עד להצגה מלאה

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

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

אחזור TTFD

כדי למצוא את המדד TTFD, צריך לשלוח קריאה ל-method reportFullyDrawn של ComponentActivity כדי לציין שהמצב הוא fully drawn. השיטה reportFullyDrawn מדווחת כשהאפליקציה מצוירת במלואה ונמצאת במצב שמאפשר שימוש. המדד TTFD הוא משך הזמן שחלף מרגע שהמערכת מקבלת את כוונת הפעלת האפליקציה ועד שמתבצעת קריאה ל-reportFullyDrawn. אם לא מתבצעת קריאה ל-reportFullyDrawn, לא מדווח ערך של TTFD.

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

כשמשתמשים ב-reportFullyDrawn, הפלט שמוצג ב-Logcat נראה כמו בדוגמה הבאה, שבה ה-TTFD הוא 1s54ms:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

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

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

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

שיפור הדיוק של תזמון ההפעלה

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

לדוגמה, אם ממשק המשתמש כולל רשימה דינמית כמו LazyColumn או LazyRow, יכול להיות שהרשימה תתמלא רק אחרי שהמסך כבר נטען במלואו – אחרי שממשק המשתמש יסומן במצב טעינה מלא. במקרים כאלה, אוכלוסיית הרשימה לא נכללת בהשוואה לשוק.

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

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

אם האפליקציה משתמשת ב-Jetpack Compose, אפשר להשתמש בממשקי ה-API הבאים כדי לציין מצב טעינה מלא:

  • ReportDrawn: מציין שהרכיב הקומפוזבילי מוכן מיידית לאינטראקציה.
  • ReportDrawnWhen: מקבל פונקציית פרדיקט, כמו list.count > 0, כדי לציין מתי הרכיב הקומפוזבילי מוכן לאינטראקציה.
  • ReportDrawnAfter: מקבל method השהיה, וכשהוא מסתיים, הוא מציין שהרכיב הקומפוזבילי מוכן לאינטראקציה.

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

class MainActivity : ComponentActivity() {

    sealed interface ActivityState {
        data object LOADING : ActivityState
        data object LOADED : ActivityState
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var activityState by remember {
                mutableStateOf(ActivityState.LOADING as ActivityState)
            }
            fullyDrawnReporter.addOnReportDrawnListener {
                activityState = ActivityState.LOADED
            }
            ReportFullyDrawnTheme {
                when(activityState) {
                    is ActivityState.LOADING -> {
                        // Display the loading UI.
                    }
                    is ActivityState.LOADED -> {
                        // Display the full UI.
                    }
                }
            }
            SideEffect {
                fullyDrawnReporter.addReporter()
                lifecycleScope.launch(Dispatchers.IO) {
                    // Perform the background operation.
                    fullyDrawnReporter.removeReporter()
                }
                fullyDrawnReporter.addReporter()
                lifecycleScope.launch(Dispatchers.IO) {
                    // Perform the background operation.
                    fullyDrawnReporter.removeReporter()
                }
            }
        }
    }
}
זיהוי צווארי בקבוק

כדי לחפש צווארי בקבוק, אפשר להשתמש ב-CPU Profiler ב-Android Studio. מידע נוסף זמין במאמר בנושא בדיקת פעילות המעבד באמצעות CPU Profiler.

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

פתרון בעיות נפוצות

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

אתחול כבד של אפליקציה

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

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

אתגרים נוספים במהלך אתחול האפליקציה כוללים אירועים של מנגנון איסוף זבל שמשפיעים על התהליך או מתרחשים במספרים גדולים, או קלט/פלט של דיסק שמתרחש במקביל לאתחול, מה שחוסם עוד יותר את תהליך האתחול. מנגנון איסוף זבל הוא שיקול חשוב במיוחד בסביבת זמן הריצה של Dalvik. סביבת זמן ריצה ל-Android‏ (ART) מבצעת מנגנון איסוף זבל במקביל, וכך מצמצמים את ההשפעה של הפעולה הזו.

אבחון הבעיה

אפשר להשתמש במעקב אחר שיטות או במעקב מוטבע כדי לנסות לאבחן את הבעיה.

תיעוד method

הפעלת הכלי CPU Profiler מגלה שהשיטה callApplicationOnCreate בסופו של דבר קוראת לשיטה com.example.customApplication.onCreate. אם הכלי מראה שהשיטות האלה לוקחות הרבה זמן לסיים את הביצוע, כדאי לבדוק מה קורה שם.

מעקב בתוך השורה

כדי לחקור את הגורמים האפשריים לבעיה, אפשר להשתמש במעקב מוטבע, כולל:

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

פתרונות לבעיה

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

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

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

אתחול של פעילות כבדה

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

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

אבחון הבעיה

גם במקרה הזה, יכול להיות שימושי לעקוב אחרי שיטות וגם אחרי שורות קוד.

תיעוד method

כשמשתמשים ב-CPU Profiler, חשוב לשים לב לבוני המחלקות (subclass) ולשיטות com.example.customApplication.onCreate של Application באפליקציה.

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

מעקב בתוך השורה

כדי לחקור את הגורמים האפשריים לבעיה, אפשר להשתמש במעקב מוטבע, כולל:

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

פתרונות לבעיה

יכולות להיות הרבה צווארי בקבוק, אבל הנה שתי בעיות נפוצות והפתרונות שלהן:

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

מידע נוסף על רה-קומפוזיציה זמין במאמרים רה-קומפוזיציה וקבלת נתוני רה-קומפוזיציה.

מסכי פתיחה בהתאמה אישית

יכול להיות שיתווסף זמן נוסף במהלך ההפעלה אם השתמשתם בעבר באחת מהשיטות הבאות כדי להטמיע מסך פתיחה בהתאמה אישית ב-Android 11 (רמת API‏ 30) או בגרסאות קודמות:

  • שימוש במאפיין העיצוב windowDisablePreview כדי להשבית את המסך הריק הראשוני שהמערכת מציירת במהלך ההפעלה.
  • שימוש ב-Activity ייעודי.

החל מ-Android 12, נדרש מעבר ל-API‏ SplashScreen. ה-API הזה מאפשר זמן הפעלה מהיר יותר, ונותן לכם אפשרות לשנות את מסך הפתיחה בדרכים הבאות:

בנוסף, ספריית התאימות מבצעת backport של SplashScreen API כדי לאפשר תאימות לדור קודם וליצור מראה ותחושה עקביים לתצוגת מסך הפתיחה בכל גרסאות Android.

פרטים נוספים זמינים במדריך להעברת מסכי פתיחה.