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

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

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

באיור 1 מוצג אופן העברת העבודה בין תהליכי המערכת והאפליקציה.

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

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

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

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

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

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

יצירת פעילות

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

תפקוד האפליקציה

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

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

במדדי 'תפקוד האפליקציה' נעשה שימוש במדד הזמן עד להצגה הראשונית (TTID). מידע על האופן שבו Google Play אוסף נתוני תפקוד האפליקציה ב-Android זמין במסמכי Play Console.

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

הזמן להצגה ראשונית (TTID) הוא הזמן שחולף עד להצגת הפריימים הראשונים של ממשק המשתמש של האפליקציה. המדד הזה מודד את הזמן שנדרש לאפליקציה ליצור את הפריים הראשון שלה, כולל אתחול התהליך במהלך הפעלה מההתחלה (cold start), יצירת הפעילות במהלך הפעלה מההתחלה או הפעלה מהזיכרון (warm start) והצגת הפריים הראשון. שמירה על ערך נמוך של 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, כמו שמוצג באיור 5. השבתת המסננים נדרשת כי השרת של המערכת, ולא האפליקציה עצמה, מציג את היומן הזה.

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

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

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

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

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

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

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

אחזור TTFD

כדי למצוא את המדד TTFD, צריך לשלוח קריאה לשיטה reportFullyDrawn() של ComponentActivity כדי לציין שהמצב הוא 'ציור מלא'. השיטה 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 עד שהאפליקציה תהיה שמישה, כדי שאכלוס הרשימה ייכלל בתזמון של ההשוואה.

לדוגמה, אם ממשק המשתמש כולל רשימה דינמית, כמו RecyclerView או רשימה עצלה (Lazy List), יכול להיות שהרשימה תתמלא רק אחרי שהמסך כבר נטען במלואו – אחרי שממשק המשתמש יסומן במצב טעינה מלאה. במקרים כאלה, אוכלוסיית הרשימה לא נכללת בהשוואה לשוק.

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

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

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

Kotlin

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()
                }
            }
        }
    }
}

Java

public class MainActivity extends ComponentActivity {
    private FullyDrawnReporter fullyDrawnReporter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fullyDrawnReporter = getFullyDrawnReporter();
        fullyDrawnReporter.addOnReportDrawnListener(() -> {
            // Trigger the UI update.
            return Unit.INSTANCE;
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();
                // Do the background work.
                fullyDrawnReporter.removeReporter();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();
                // Do the background work.
                fullyDrawnReporter.removeReporter();
            }
        }).start();
    }
}

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

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

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

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

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

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

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

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

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

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

אבחון הבעיה

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

תיעוד method

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

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

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

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

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

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

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

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

אתחול של פעילות אינטנסיבית

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

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

אבחון הבעיה

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

תיעוד method

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

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

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

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

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

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

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

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

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

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

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

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

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

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