המשתמשים מצפים שהאפליקציות ייטענו במהירות ויהיו רספונסיביות. אפליקציה עם זמן הפעלה איטי לא עומדת בציפייה הזו, והמשתמשים עלולים להתאכזב. חוויה גרועה כזו עלולה לגרום למשתמש לדרג את האפליקציה שלכם בדירוג נמוך בחנות Play או אפילו להפסיק להשתמש בה לגמרי.
בדף הזה מוסבר איך לשפר את זמן ההפעלה של האפליקציה, כולל סקירה כללית של התהליכים הפנימיים של תהליך ההפעלה, איך ליצור פרופיל של ביצועי ההפעלה ובעיות נפוצות שקשורות לזמן ההפעלה, וטיפים לפתרון הבעיות האלה.
הסבר על מצבי ההפעלה השונים של האפליקציה
הפעלת האפליקציה יכולה להתבצע באחד משלושה מצבים: הפעלה במצב התחלתי (cold start), הפעלה במצב ביניים (Warm start) או הפעלה מתוך הזיכרון (Hot start). כל סטטוס משפיע על משך הזמן שנדרש עד שהאפליקציה תהיה גלויה למשתמש. במצב התחלתי (cold start), האפליקציה מתחילה מאפס. במצבים אחרים, המערכת צריכה להעביר את האפליקציה הפועלת מהרקע לחזית.
מומלץ לבצע אופטימיזציה תמיד על סמך ההנחה של הפעלה במצב התחלתי (cold start). הפעולה הזו יכולה גם לשפר את הביצועים של הפעלות חמות וקרות.
כדי לבצע אופטימיזציה של האפליקציה להפעלה מהירה, כדאי להבין מה קורה ברמת המערכת וברמת האפליקציה, ואיך הן פועלות יחד בכל אחד מהמצבים האלה.
שני מדדים חשובים לקביעת זמן ההפעלה של האפליקציה הם הזמן עד להצגה הראשונית (TTID) והזמן עד להצגה מלאה (TTFD). המדד TTID הוא הזמן שנדרש להצגת הפריימים הראשונים, והמדד TTFD הוא הזמן שנדרש עד שהאפליקציה מאפשרת אינטראקציה מלאה. שני המדדים חשובים באותה מידה, כי TTID מאפשר למשתמש לדעת שהאפליקציה נטענת, ו-TTFD הוא הזמן שבו האפליקציה באמת ניתנת לשימוש. אם אחד מהם ארוך מדי, יכול להיות שהמשתמש יצא מהאפליקציה לפני שהיא תיטען במלואה.
הפעלה במצב התחלתי (cold start)
הפעלה במצב התחלתי (cold start) מתייחסת להפעלה של אפליקציה מאפס. כלומר, עד לתחילת התהליך, התהליך של המערכת יוצר את התהליך של האפליקציה. הפעלות קרות מתרחשות במקרים כמו הפעלת האפליקציה בפעם הראשונה מאז שהמכשיר הופעל או מאז שהמערכת סגרה את האפליקציה.
התחלה מהסוג הזה היא האתגר הגדול ביותר לצמצום זמן ההפעלה, כי המערכת והאפליקציה צריכות לבצע יותר עבודה מאשר במצבי ההפעלה האחרים.
בתחילת הפעלה במצב התחלתי (cold start), המערכת מבצעת את שלוש המשימות הבאות:
- טוענים את האפליקציה ומפעילים אותה.
- להציג חלון התחלתי ריק של האפליקציה מיד אחרי ההפעלה.
- יוצרים את התהליך של האפליקציה.
ברגע שהמערכת יוצרת את תהליך האפליקציה, התהליך הזה אחראי לשלבים הבאים:
- יוצרים את אובייקט האפליקציה.
- הפעלת ה-thread הראשי.
- יוצרים את הפעילות הראשית.
- מאתחלים את ממשק המשתמש.
- מציירים את ממשק המשתמש על המסך.
כשתהליך האפליקציה משלים את הציור הראשון, תהליך המערכת מחליף את חלון הרקע המוצג בפעילות הראשית. בשלב הזה, המשתמש יכול להתחיל להשתמש באפליקציה.
מידע נוסף על שלבי הפריסה ב-Compose זמין במאמר שלבים ב-Jetpack Compose.
בעיות בביצועים יכולות לקרות במהלך יצירת האפליקציה ויצירת פעילות המארח.
יצירת אפליקציה
כשהאפליקציה מופעלת, חלון ההתחלה הריק נשאר על המסך עד שהמערכת מסיימת לצייר את האפליקציה בפעם הראשונה. בשלב הזה, תהליך המערכת מחליף את חלון ההתחלה של האפליקציה, ומאפשר למשתמש ליצור אינטראקציה עם האפליקציה.
אם אתם מבטלים את Application.onCreate באפליקציה שלכם, המערכת מפעילה את השיטה onCreate באובייקט האפליקציה. לאחר מכן, האפליקציה יוצרת את שרשור ה-UI הראשי, שנקרא גם UI thread, ומטילה עליו את המשימה של יצירת פעילות המארח של האפליקציה.
מנקודה זו, התהליכים ברמת המערכת והאפליקציה ממשיכים בהתאם לשלבים במחזור החיים של האפליקציה.
יצירת פעילות
אחרי שתהליך האפליקציה יוצר את הפעילות, הפעילות מבצעת את הפעולות הבאות:
- מאפס את הערכים.
- קורא ל-constructors.
- מפעיל את שיטת הקריאה החוזרת, כמו
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). תהליך המערכת מציג מסך ריק עד שהאפליקציה מסיימת לעבד את הפעילות.
איך מזהים את הפעלת האפליקציה ב-Perfetto
כדי לנפות באגים בבעיות בהפעלת האפליקציה, כדאי להבין מה בדיוק כלול בשלב ההפעלה של האפליקציה. כדי לזהות את כל שלב ההפעלה של האפליקציה ב-Perfetto, פועלים לפי השלבים הבאים:
ב-Perfetto, מוצאים את השורה עם המדד הנגזר Android App Startups. אם לא רואים את האפשרות הזו, מנסים ללכוד נתונים באמצעות אפליקציית מעקב המערכת במכשיר.
איור 2. הפרוסה של מדד ההפעלה של אפליקציות ל-Android ב-Perfetto. לוחצים על הפלח המשויך ומקישים על m כדי לבחור את הפלח. סוגריים מופיעים מסביב לפלח ומציינים כמה זמן נמשך הפלח. המשך מוצג גם בכרטיסייה הבחירה הנוכחית.
כדי להצמיד את השורה Android App Startups (הפעלות של אפליקציות ל-Android), לוחצים על סמל ההצמדה שמופיע כשמעבירים את מצביע העכבר מעל השורה.
גוללים לשורה עם האפליקציה הרלוונטית ולוחצים על התא הראשון כדי להרחיב את השורה.
מקישים על w כדי להתקרב לשרשור הראשי, בדרך כלל בחלק העליון (מקישים על s, a, d כדי להתרחק, לזוז ימינה ולזוז שמאלה, בהתאמה).
איור 3. הפלח של מדד ההפעלה של אפליקציית Android מוצג ליד ה-thread הראשי של האפליקציה. הפלח של המדדים הנגזרים מאפשר לכם לראות בקלות מה בדיוק כלול בהפעלת האפליקציה, כדי שתוכלו להמשיך לנפות באגים בצורה מפורטת יותר.
כשמנתחים אפליקציית Jetpack Compose, אפשר להשתמש במעקב אחר קומפוזיציה כדי לראות פרטים מדויקים על הביצועים של ממשק המשתמש.
חשוב לשים לב במיוחד לקטעי ה-trace בשרשור הראשי שמתחילים ב-Choreographer#doFrame. מחפשים פלחים כמו Compose:recompose, Compose:layout ו-Compose:draw. הם מראים כמה זמן האפליקציה משקיעה בהרכבה, במדידה, במיקום ובציור של רכיבים ספציפיים.
שימוש במדדים כדי לבדוק ולשפר את זמני האתחול
כדי לאבחן בצורה נכונה את הביצועים של זמן ההפעלה, אפשר לעקוב אחרי מדדים שמראים כמה זמן לוקח לאפליקציה להתחיל לפעול. מערכת Android מספקת כמה דרכים להצגת בעיה באפליקציה ולעזרה באבחון שלה. תפקוד האפליקציה יכול להתריע על בעיה שמתרחשת, וכלי הניתוחים יכולים לעזור לכם לאבחן את הבעיה.
היתרונות של שימוש במדדים של חברות סטארט-אפ
Android משתמש במדדים זמן להצגה ראשונית (TTID) וזמן להצגה מלאה (TTFD) כדי לבצע אופטימיזציה של הפעלת אפליקציות במצב 'הפעלה קרה' ו'הפעלה חמה'. סביבת זמן הריצה ל-Android (ART) משתמשת בנתונים מהמדדים האלה כדי לבצע קומפילציה מראש של קוד בצורה יעילה, לצורך אופטימיזציה של הפעלות עתידיות.
הפעלה מהירה יותר מובילה לאינטראקציה ממושכת יותר של המשתמשים עם האפליקציה, וכך מצטמצמים המקרים של יציאה מוקדמת, הפעלה מחדש של המופע או מעבר לאפליקציה אחרת.
תפקוד האפליקציה
הנתונים של מדד תפקוד האפליקציה יכולים לעזור לכם לשפר את הביצועים שלה. לשם כך, תקבלו התראות ב-Play Console אם זמני ההפעלה של האפליקציה מוגזמים.
הנתונים של מדד תפקוד האפליקציה מציינים שהזמנים הבאים להפעלת האפליקציה הם מוגזמים:
- ההפעלה במצב התחלתי (cold startup) נמשכת 5 שניות או יותר.
- הפעלה במצב ביניים (warm start) נמשכת 2 שניות או יותר.
- הפעלה מתוך הזיכרון (Hot start) נמשכת 1.5 שניות או יותר.
במדדי 'תפקוד האפליקציה' נעשה שימוש במדד הזמן להצגה ראשונית (TTID). מידע על האופן שבו Google Play אוסף נתונים של תפקוד האפליקציה ב-Android זמין במסמכי Play Console.
הזמן עד להצגה הראשונית
הזמן להצגה ראשונית (TTID) הוא הזמן שנדרש להצגת הפריימים הראשונים של ממשק המשתמש של האפליקציה. המדד הזה מודד את הזמן שנדרש לאפליקציה ליצור את הפריים הראשון שלה, כולל אתחול התהליך במהלך הפעלה מההתחלה (cold start), יצירת פעילות במהלך הפעלה מההתחלה או הפעלה מהזיכרון (warm start) והצגת הפריים הראשון. שמירה על ערך נמוך של TTID באפליקציה עוזרת לשפר את חוויית המשתמש, כי המשתמשים יכולים לראות את האפליקציה נפתחת במהירות. המסגרת של Android מדווחת על TTID באופן אוטומטי לכל אפליקציה. כשמבצעים אופטימיזציה של הפעלת האפליקציה, מומלץ להטמיע את
reportFullyDrawn כדי לקבל מידע עד TTFD.
הזמן שחלף עד לזיהוי (TTID) נמדד כערך זמן שמייצג את הזמן הכולל שחלף, שכולל את רצף האירועים הבא:
- הפעלת התהליך.
- מתבצע אתחול של האובייקטים.
- יצירה ואתחול של פעילות המארח.
- אתחול ממשק המשתמש.
- ציור האפליקציה בפעם הראשונה.
אחזור TTID
כדי למצוא את TTID, מחפשים בכלי Logcat בשורת הפקודה שורת פלט שמכילה ערך בשם Displayed. הערך הזה הוא TTID והוא דומה לדוגמה הבאה, שבה TTID הוא 3s534ms:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
כדי למצוא את TTID ב-Android Studio, משביתים את המסננים בתצוגת Logcat מהתפריט הנפתח של המסננים, ואז מוצאים את השעה Displayed, כמו שמוצג באיור 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. ה-method 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, האפליקציה משתמשת רק בחלק קטן מנתוני המצב שאותחלו קודם.
אתגרים נוספים במהלך אתחול האפליקציה כוללים אירועים של מנגנון איסוף זבל (garbage collection) שמשפיעים על התהליך או מתרחשים במספרים גדולים, או קלט/פלט של דיסק שמתרחש במקביל לאתחול, מה שחוסם עוד יותר את תהליך האתחול. מנגנון איסוף זבל הוא שיקול חשוב במיוחד בסביבת זמן הריצה של 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 הזה מאפשר זמן הפעלה מהיר יותר, ונותן לכם אפשרות לשנות את מסך הפתיחה בדרכים הבאות:
- מגדירים עיצוב כדי לשנות את המראה של מסך הפתיחה.
- שליטה במשך הזמן שבו מסך הפתיחה מוצג באמצעות
windowSplashScreenAnimationDuration. - התאמה אישית של האנימציה של מסך הפתיחה, וטיפול חלק באנימציה של סגירת מסך הפתיחה.
בנוסף, ספריית התאימות מבצעת backport של SplashScreen API כדי לאפשר תאימות לדור קודם וליצור מראה ותחושה עקביים של מסך הפתיחה בכל גרסאות Android.
פרטים נוספים זמינים במדריך להעברת מסכי פתיחה.
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- רינדור איטי
- איסוף מדדים של ספריית Macrobenchmark
- יצירת פרופיל Baseline{:#creating-profile-rules}