האופן שבו מנהלים את ההיררכיה של אובייקטי View
יכול להשפיע באופן משמעותי על ביצועי האפליקציה. בדף הזה מוסבר איך להעריך אם היררכיית התצוגה מאטה את האפליקציה, ומפורטות כמה אסטרטגיות לטיפול בבעיות שעשויות להתעורר.
הדף הזה מתמקד בשיפור פריסות שמבוססות על View
. מידע נוסף על שיפור הביצועים של Jetpack פיתוח נייטיב זמין במאמר ביצועים של Jetpack פיתוח נייטיב.
ביצועים של פריסה ומדידה
צינור עיבוד הנתונים לעיבוד הגרפי כולל שלב של פריסה ומדידה, שבמהלכו המערכת ממוקמת את הפריטים הרלוונטיים בהיררכיית התצוגה בצורה מתאימה. החלק measure בשלב הזה קובע את הגדלים והגבולות של אובייקטים מסוג View
. החלק layout קובע איפה במסך יוצגו אובייקטי ה-View
.
בשני השלבים האלה בצינור עיבוד הנתונים יש עלות קטנה יחסית לכל צפייה או פריסה שעוברים עיבוד. ברוב המקרים, העלות הזו היא מזערית ואין לה השפעה משמעותית על הביצועים. עם זאת, המכסה יכולה להיות גדולה יותר כשאפליקציה מוסיפה או מסירה אובייקטים של View
, למשל כשאובייקט RecyclerView
ממחזר אותם או עושה בהם שימוש חוזר. העלות יכולה להיות גבוהה יותר גם אם צריך לשנות את הגודל של אובייקט View
כדי לעמוד באילוצים שלו. לדוגמה, אם האפליקציה שלכם קורא ל-SetText()
על אובייקט View
שמאריך טקסט, יכול להיות שיהיה צורך לשנות את הגודל של View
.
אם מקרים כאלה נמשכים יותר מדי זמן, הם עלולים למנוע רינדור של פריים בתוך 16 אלפיות השנייה המותרות, וכתוצאה מכך פריימים עשויים להישמט והאנימציה עשויה להיות קטועה.
אי אפשר להעביר את הפעולות האלה לשרשור עבודה – האפליקציה צריכה לעבד אותן בשרשור הראשי – לכן עדיף לבצע אופטימיזציה שלהן כדי שהן ייקחו כמה שפחות זמן.
ניהול פריסות מורכבות
פריסות ב-Android מאפשרות להטמיע אובייקטים של ממשק משתמש בהיררכיית התצוגות. סידור כזה יכול להטיל גם עלות פריסה. כשהאפליקציה מעבדת אובייקט לצורך פריסה, היא מבצעת את אותו תהליך גם בכל הצאצאים של הפריסה.
בפריסת מודעות מורכבת, לפעמים העלות נצברת רק בפעם הראשונה שהמערכת מחשבת את הפריסה. לדוגמה, כשהאפליקציה שלכם ממחזרת פריט מורכב ברשימה באובייקט RecyclerView
, המערכת צריכה לפרסם את כל האובייקטים. דוגמה נוספת: שינויים טריוויאליים יכולים להתפשט במורד השרשרת לכיוון ההורה, עד שהם מגיעים לאובייקט שלא משפיע על הגודל של ההורה.
סיבה נפוצה לכך שהפריסה נמשכת זמן רב היא כשהיררכיות של אובייקטים מסוג View
מוערמות זו בתוך זו. כל אובייקט פריסה בתצוגת עץ מוסיף עלות לשלב הפריסה. ככל שההיררכיה שלכם שטוחה יותר, כך זמן השלמת שלב הפריסה יהיה קצר יותר.
מומלץ להשתמש בעורך הפריסה כדי ליצור ConstraintLayout
, במקום RelativeLayout
או LinearLayout
, כי בדרך כלל הוא יעיל יותר וגם מצמצם את מיקום הפריסות. עם זאת, לפריסות פשוטות שאפשר ליצור באמצעות FrameLayout
, מומלץ להשתמש ב-FrameLayout
.
אם אתם משתמשים במחלקה RelativeLayout
, יכול להיות שתוכלו להשיג את אותה
השפעה בעלות נמוכה יותר באמצעות שימוש בתצוגות LinearLayout
לא משוקללות במקום זאת. עם זאת, אם אתם משתמשים בתצוגות LinearLayout
מוערלות בתוך תצוגות אחרות, עלות הפריסה גבוהה בהרבה כי נדרשים כמה מעברי פריסה, כפי שמוסבר בקטע הבא.
מומלץ גם להשתמש ב-RecyclerView
במקום ב-ListView
, כי הוא מאפשר לעשות שימוש חוזר בפריסות של פריטים ספציפיים ברשימה. כך אפשר לשפר את היעילות ואת הביצועים של גלילה.
מיסוי כפול
בדרך כלל, המסגרת מבצעת את שלב הפריסה או המדידה בפעולה אחת. עם זאת, במקרים מסוימים של פריסה מורכבת, יכול להיות שהמסגרת תצטרך לבצע כמה חזרות על חלקים בהיררכיה שדורשים כמה סבבים כדי לפתור אותם, לפני שהיא תוכל למקם את הרכיבים בסופו של דבר. לבצע יותר מאיטרציה אחת של פריסה ומדידה נקראת מיסוי כפול.
לדוגמה, כשמשתמשים בקונטיינר RelativeLayout
, שמאפשר למקם אובייקטים מסוג View
ביחס למיקומים של אובייקטים אחרים מסוג View
, המסגרת מבצעת את התהליך הבא:
- מפעיל אישור של פריסה ומדידה, שבמהלכו ה-framework מחשב את המיקום והגודל של כל אובייקט צאצא, על סמך הבקשה של כל אחד מהילדים.
- המערכת משתמשת בנתונים האלה, תוך התחשבות במשקלים של הפריטים, כדי לקבוע את המיקום המתאים של תצוגות קשורות.
- ביצוע סבב נוסף של פריסה כדי לקבוע סופית את המיקומים של האובייקטים.
- מעבר לשלב הבא בתהליך העיבוד.
ככל שיש יותר רמות בהיררכיית התצוגה, כך יש סיכוי גבוה יותר לקבלת פגיעה בביצועים.
כפי שציינו קודם, ConstraintLayout
בדרך כלל יעיל יותר מפריסות אחרות מלבד FrameLayout
. היא פחות נוטה לעבור כמה פעמים על הפריסה, ובמקרים רבים מבטלת את הצורך להטמיע פריסות.
גם קונטיינרים שאינם RelativeLayout
עלולים להגדיל את הסיכון לחיוב כפול. לדוגמה:
- אם תגדירו תצוגה
LinearLayout
כאופקית, יכול להיות שתתבצע שתי פעולות של פריסה ומדידה. ייתכן שגם בכיוון אנכי תתבצע שתי פעולות של פריסה ומדידה אם מוסיפים את הערךmeasureWithLargestChild
. במקרה כזה, יכול להיות שהמסגרת תצטרך לבצע סבב נוסף כדי לקבוע את הגדלים המתאימים של האובייקטים. - ה-
GridLayout
מאפשר גם מיקום יחסי, אבל בדרך כלל הוא מונע מצב של מיסוי כפול על ידי עיבוד מראש של יחסי המיקום בין תצוגות הצאצאים. עם זאת, אם בפריסה נעשה שימוש במשקלים או במילוי עם הכיתהGravity
, היתרון של העיבוד המקדים מתבטל, ויכול להיות שהמסגרת תצטרך לבצע כמה סבבים אם המאגר הואRelativeLayout
.
אישורים מרובים של פריסה ומדידה לא בהכרח מהווים נטל ביצועים. עם זאת, הם יכולים להפוך לעול אם הם נמצאים במקום הלא נכון. חשוב להיזהר במצבים שבהם אחד מהתנאים הבאים מתקיים על הקונטיינר:
- זהו אלמנט ברמה הבסיסית (root) בהיררכיית התצוגה.
- מתחתיו יש היררכיית תצוגה עמוקה.
- יש הרבה מופעים שלו שמיישבים את המסך, בדומה לצאצאים באובייקט
ListView
.
אבחון בעיות בהיררכיית התצוגות
ביצועי הפריסה הם בעיה מורכבת עם הרבה היבטים. הכלים הבאים יכולים לעזור לכם לזהות את נקודות הצוואר בבקבוק בביצועים. חלק מהכלים מספקים מידע פחות סופי, אבל יכולים לספק רמזים מועילים.
Perfetto
Perfetto הוא כלי שמספק נתונים על הביצועים. אפשר לפתוח את הטראסים של Android בממשק המשתמש של Perfetto.
עיבוד פרופיל ב-GPU
הכלי לרינדור GPU של הפרופיל במכשיר, שזמין במכשירים עם Android 6.0 (API ברמה 23) ואילך, יכול לספק לכם מידע קונקרטי על צווארי בקבוק בביצועים. הכלי הזה מאפשר לראות כמה זמן נמשך שלב הפריסה והמדידה בכל פריים של רינדור. הנתונים האלה יכולים לעזור לכם לאבחן בעיות בביצועים בסביבת זמן הריצה, ולקבוע אילו בעיות שקשורות לפריסה ולמדידה צריך לטפל בהן.
בייצוג הגרפי של הנתונים שהיא מתעדת, הרינדור של GPU של הפרופיל משתמש בצבע הכחול שמייצג את זמן הפריסה. למידע נוסף על אופן השימוש בכלי הזה, ראו מהירות הרינדור של GPU של הפרופיל.
Lint
הכלי Lint ב-Android Studio יכול לעזור לכם לזהות יעילות נמוכה בהיררכיית התצוגה. כדי להשתמש בכלי הזה, בוחרים באפשרות Analyze (ניתוח) > Inspect Code (בדיקת קוד), כפי שמוצג באיור 1.
מידע על פריטים שונים של פריסה מופיע בקטע Android > Lint > Performance. כדי לראות פרטים נוספים, לוחצים על כל פריט כדי להרחיב אותו ולהציג מידע נוסף בחלונית שבצד שמאל של המסך. איור 2 מציג דוגמה למידע מורחב.
לחיצה על פריט חושפת בעיות הקשורות לאותו פריט בחלונית שמשמאל.
במשאבי העזרה של Lint תוכלו לקרוא מידע נוסף על נושאים ובעיות ספציפיים באזור הזה.
כלי לבדיקת פריסות
הכלי Layout Inspector ב-Android Studio מספק ייצוג חזותי של היררכיית התצוגה של האפליקציה. זוהי דרך טובה לנווט בהיררכיה של האפליקציה, ומספקת ייצוג חזותי ברור של שרשרת ההורה של תצוגה מסוימת, ומאפשרת לבדוק את הפריסות שהאפליקציה יוצרת.
גם התצוגות של 'בודק הפריסה' יכולות לעזור בזיהוי בעיות בביצועים שנובעות ממיסוי כפול. הוא גם יכול לספק לכם דרך לזהות שרשראות עמוקות של פריסות בתצוגת עץ, או אזורים של פריסות עם כמות גדולה של רכיבים בתצוגת עץ, שיכולים להיות מקור לעלויות ביצועים. במקרים כאלה, השלבים של יצירת הפריסה והמדידה יכולים להיות יקרים ולגרום לבעיות בביצועים.
מידע נוסף זמין במאמר ניפוי באגים בפריסת האתר באמצעות Layout Inspector ו-Layout Validation.
פתרון בעיות בהיררכיית התצוגות
לפעמים קשה ליישם את הקונספט הבסיסי של פתרון בעיות ביצועים שנובעות מהיררכיות של תצוגות. כדי למנוע מהיררכיות תצוגה להטיל קנסות על ביצועים, צריך להשטיח את היררכיית התצוגה ולצמצם את הכפלת המס. החלק הזה דן באסטרטגיות להשגת היעדים האלה.
הסרה של פריסות עץ מיותרות
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
, וגם תכונות נוספות שיעזרו לכם ליצור פריסות שטוחות ויעילות יותר.