ניתוח ואופטימיזציה של הפעלת האפליקציה

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

מומלץ להשתמש בספריית Macrobenchmark כדי למדוד את ההפעלה. הספרייה מספקת סקירה כללית ומעקב מפורט אחרי המערכת כדי לראות בדיוק מה קורה במהלך ההפעלה.

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

כדי לנתח את הפעלת האפליקציה:

שלבים לניתוח האתחול ולאופטימיזציה שלו

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

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

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

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

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

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

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

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

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

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

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

  • הם תופסים פרקי זמן ארוכים ואפשר לבצע אופטימיזציה שלהם. כל אלפית שנייה חשובה בביצועים. לדוגמה, אפשר לחפש את Choreographer זמני הציור, זמני ההרכבה הראשוניים, זמני הטעינה של הספרייה, עסקאות Binder או זמני הטעינה של המשאבים. כדי להתחיל, כדאי לבדוק את כל הפעולות שנמשכות יותר מ-20 אלפיות השנייה.
  • לחסום את ה-thread הראשי. מידע נוסף מופיע במאמר בנושא ניווט בדוח Systrace.
  • אין צורך להפעיל אותו במהלך ההפעלה.
  • אפשר לחכות עד שהפריים הראשון מצויר.

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

זיהוי פעולות יקרות בשרשור הראשי

מומלץ להימנע מפעולות יקרות כמו קלט/פלט של קבצים וגישה לרשת ב-thread הראשי. זה חשוב באותה מידה גם במהלך הפעלת האפליקציה, כי פעולות יקרות בשרשור הראשי עלולות לגרום לאפליקציה לא להגיב ולעכב פעולות קריטיות אחרות. ‫StrictMode.ThreadPolicy יכול לעזור לזהות מקרים שבהם מתרחשות פעולות יקרות בשרשור הראשי. מומלץ להפעיל את StrictMode בגרסאות של ניפוי באגים כדי לזהות בעיות מוקדם ככל האפשר, כמו בדוגמה הבאה:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

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

TTID ו-TTFD

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

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

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

ניתוח המצב הכללי של השרשור

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

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

זיהוי של חלקים גדולים במצב שינה של השרשור הראשי

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

צמצום החסימה של ה-thread הראשי והשינה שלא ניתנת להפרעה

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

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

שיפור זמן ההפעלה

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

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

ניתוח הביצועים של ממשק המשתמש

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

הגבלת העבודה במהלך האתחול

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

כדי לבצע אופטימיזציה של ההפעלה:

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

מדידת נתוני מסגרת

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

  • איסוף מקומי באמצעות dumpsys gfxinfo: לא כל המסגרות שנצפו בנתוני dumpsys אחראיות לרינדור האיטי של האפליקציה או משפיעות על משתמשי הקצה. עם זאת, זהו מדד טוב לבדיקה לאורך מחזורי הפצה שונים כדי להבין את מגמת הביצועים הכללית. במאמר יסודות הבדיקה של אפליקציות ל-Android מוסבר איך להשתמש ב-gfxinfo וב-framestats כדי לשלב מדידות של ביצועי ממשק המשתמש בשיטות הבדיקה.
  • איסוף שדות באמצעות JankStats: אפשר לאסוף נתונים על זמני רינדור של פריימים מחלקים ספציפיים באפליקציה באמצעות ספריית JankStats, ולתעד ולנתח את הנתונים.
  • בבדיקות באמצעות Macrobenchmark (Perfetto מתחת לפני השטח)
  • Perfetto FrameTimeline: ב-Android 12 (רמת API‏ 31) ומעלה, אפשר לאסוף מדדים של ציר הזמן של הפריימים ממעקב של Perfetto כדי לזהות איזו פעולה גורמת לירידה במספר הפריימים. זה יכול להיות השלב הראשון באבחון הסיבה להשמטת פריימים.
  • כלי הפרופיל של Android Studio לזיהוי תקלות

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

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

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

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

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

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

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

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

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

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