היררכיות של ביצועים וצפיות

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

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

ביצועים של פריסה ומדידה

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

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

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

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

ניהול פריסות מורכבות

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

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

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

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

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

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

מיסוי כפול

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

לדוגמה, כשמשתמשים במאגר התגים RelativeLayout, שמאפשר למקם View אובייקטים ביחס למיקומים של אובייקטים אחרים מסוג View, framework מבצע את הרצף הבא:

  1. מפעיל אישור של פריסה ומדידה, שבמהלכו ה-framework מחשב את הערך של כל אובייקט צאצא המיקום והגודל, בהתאם לבקשה של כל אחד מהילדים.
  2. משתמשת בנתונים האלה תוך התחשבות במשקולות של חפצים כדי לקבוע את המיקום הנכון ודעות מתואמות.
  3. מבצע אישור פריסה שני כדי להשלים את האובייקטים במיקומי המודעות החדשים.
  4. מעבר לשלב הבא בתהליך הרינדור.

ככל שהיררכיית הצפיות כוללת יותר רמות, כך גדל הסיכוי לפגיעה בביצועים.

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

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

  • תצוגת LinearLayout יכולה להוביל לקבלת אישור כפול של פריסה ומדידה אם מבצעים את התבנית לרוחב. ייתכן שיוצג אישור כפול של פריסה ומדידה גם בכיוון אנכי אם הוספה measureWithLargestChild, במקרה כזה ייתכן שהמסגרת תצטרך לבצע מעבר שני כדי לזהות את הגדלים המתאימים אובייקטים.
  • גם GridLayout מאפשרת מיקום יחסי, אבל בדרך כלל היא מונעת מיסוי כפול על ידי עיבוד מראש של יחסי מיקום בין צפיות ילדים. עם זאת, אם הפריסה כוללת משקולות או כיתה אחת (Gravity), היתרון של עיבוד מראש יאבד, וייתכן שה-framework תצטרך לבצע מספר אישורים אם הקונטיינר הוא RelativeLayout.

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

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

אבחון בעיות בהיררכיית התצוגה

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

פרפטו

Perfetto הוא כלי שמספק נתונים לגבי ביצועים. ניתן לפתוח מעקבים של Android ב-Perfetto UI.

עיבוד פרופיל ב-GPU

רינדור ה-GPU של הפרופיל במכשיר שזמין במכשירים המבוססים על Android 6.0 (רמת API 23) ואילך, יכול לספק מידע קונקרטי על צווארי בקבוק בביצועים. הכלי הזה מאפשר לראות כמה זמן מתבצע שלב של פריסה ומדידה, מסגרת הרינדור. הנתונים האלה יכולים לעזור לך לאבחן בעיות בביצועים בזמן הריצה להבין באילו בעיות של פריסה ומדידה צריך לטפל.

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

מוך

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

איור 1. בוחרים באפשרות בדיקת קוד ב-Android Studio.

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

איור 2. הצגת מידע על בעיות ספציפיות של הכלי לאיתור שגיאות בקוד (lint) מזהה.

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

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

הכלי לבדיקת פריסות

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

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

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

פתרון בעיות בהיררכיית התצוגה

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

הסרה של פריסות מקננות מיותרות

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

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

התחלת השימוש במיזוג או הכללה

פעמים רבות לפריסות מקננות מיותרות הן <include> התיוג. לדוגמה, אפשר להגדיר פריסה לשימוש חוזר באופן הבא:

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

לאחר מכן אפשר להוסיף תג <include> כדי להוסיף את הפריט הבא לתבנית ההורה מאגר:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

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

<merge> יכול לעזור למנוע את הבעיה הזו. למידע נוסף על התג הזה, אפשר לעיין במאמר שימוש בתכונה <Merge> .

משתמשים בפריסה זולה יותר

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

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