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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

יצירת פעילות

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

יתרונות השימוש במדדי ההפעלה

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

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

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

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

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

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

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

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. ה-method‏ reportFullyDrawn מדווחת כשהאפליקציה משורטטת במלואה ובמצב שמיש. המדד TTFD הוא משך הזמן שעובר מהרגע שהמערכת מקבלת את ה-Intent להפעלת האפליקציה ועד שמתבצעת קריאה ל-reportFullyDrawn. אם לא שולחים קריאה ל-reportFullyDrawn, לא מדווח ערך TTFD.

כדי למדוד את ה-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 מגלה שה-method‏ callApplicationOnCreate קוראת בסופו של דבר ל-method‏ com.example.customApplication.onCreate. אם הכלי מראה שלוקח הרבה זמן להשלים את השיטות האלה, כדאי לבדוק מה קורה שם.

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

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

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

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

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

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

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

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

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

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

אבחון הבעיה

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

תיעוד method

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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