מושגים ויישום ב-Jetpack Compose
החל מ-Android 3.0 (רמת API 11), צינור העיבוד של Android 2D תומך בשיפור מהירות באמצעות חומרה, כלומר כל פעולות הציור שמבוצעות בבד הציור של View משתמשות ב-GPU.
בגלל הגידול במשאבים שנדרשים להפעלת האצת חומרה, האפליקציה תצרוך יותר RAM.
התכונה 'שיפור מהירות באמצעות חומרה' מופעלת כברירת מחדל אם רמת ה-API המיועדת היא >=14, אבל אפשר גם להפעיל אותה באופן מפורש. אם האפליקציה שלכם משתמשת רק בתצוגות רגילות וב-Drawables, הפעלת ההגדרה באופן גלובלי לא אמורה לגרום לאפקטים שליליים של ציור. עם זאת, מכיוון שהאצת חומרה לא נתמכת בכל פעולות הציור הדו-ממדיות, הפעלת ההגדרה הזו עלולה להשפיע על חלק מהתצוגות המותאמות אישית או על קריאות הציור. הבעיות בדרך כלל מתבטאות ברכיבים בלתי נראים, בחריגים או בפיקסלים שמוצגים בצורה שגויה. כדי לפתור את הבעיה, מערכת Android מאפשרת להפעיל או להשבית את שיפור המהירות באמצעות חומרה בכמה רמות. איך שולטים בשיפור המהירות באמצעות חומרה
אם האפליקציה מבצעת ציור בהתאמה אישית, כדאי לבדוק אותה במכשירי חומרה אמיתיים עם שיפור מהירות באמצעות חומרה כדי לאתר בעיות. בקטע תמיכה בפעולות שרטוט מפורטות בעיות מוכרות שקשורות לשיפור המהירות באמצעות חומרה, ומוסבר איך לעקוף אותן.
אפשר גם לעיין במאמרים בנושא OpenGL עם ממשקי Framework API ו-Renderscript.
שליטה בשיפור המהירות באמצעות חומרה
אפשר לשלוט בשיפור מהירות באמצעות חומרה ברמות הבאות:
- אפליקציה
- פעילות
- חלון
- הצגה
רמת האפליקציה
בקובץ המניפסט של Android, מוסיפים את המאפיין הבא לתג <application> כדי להפעיל האצת חומרה לכל האפליקציה:
<application android:hardwareAccelerated="true" ...>
רמת הפעילות
אם האפליקציה לא פועלת בצורה תקינה כשהאצת החומרה מופעלת באופן גלובלי, אפשר לשלוט בה גם עבור פעילויות ספציפיות. כדי להפעיל או להשבית את שיפור המהירות באמצעות חומרה ברמת הפעילות, אפשר להשתמש במאפיין android:hardwareAccelerated של הרכיב <activity>. בדוגמה הבאה, שיפור המהירות באמצעות חומרה מופעל לכל האפליקציה, אבל מושבת לפעילות אחת:
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
רמת החלון
אם אתם רוצים שליטה מדויקת יותר, אתם יכולים להפעיל שיפור מהירות באמצעות חומרה עבור חלון מסוים באמצעות הקוד הבא:
Kotlin
window.setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED )
Java
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
רמת התצוגה המפורטת
אפשר להשבית את האצת החומרה לתצוגה ספציפית בזמן הריצה באמצעות הקוד הבא:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
איך קובעים אם תצוגה מואצת על ידי חומרה
לפעמים כדאי שאפליקציה תדע אם היא מואצת כרגע על ידי חומרה, במיוחד במקרים כמו תצוגות מותאמות אישית. השיטה הזו שימושית במיוחד אם האפליקציה מבצעת הרבה פעולות ציור בהתאמה אישית, ולא כל הפעולות נתמכות בצורה תקינה בצינור העיבוד החדש.
יש שתי דרכים שונות לבדוק אם האפליקציה מאיצה את החומרה:
הפונקציה
View.isHardwareAcceleratedמחזירהtrueאםViewמצורף לחלון שמוגדר בו שיפור מהירות באמצעות חומרה.הפונקציה
Canvas.isHardwareAcceleratedמחזירה את הערךtrueאםCanvasמואץ באמצעות חומרה.
אם אתם חייבים לבצע את הבדיקה הזו בקוד הציור, כדאי להשתמש ב-Canvas.isHardwareAccelerated במקום ב-View.isHardwareAccelerated כשזה אפשרי. כשמצורף תצוגה לחלון עם האצת חומרה, עדיין אפשר לצייר אותה באמצעות Canvas ללא האצת חומרה. לדוגמה, זה קורה כשמציירים תצוגה לתוך מפת סיביות למטרות שמירה במטמון.
מודלים של ציורים ב-Android
כשהאצת החומרה מופעלת, מסגרת Android משתמשת במודל חדש של ציור שמסתמך על רשימות תצוגה כדי להציג את האפליקציה במסך. כדי להבין באופן מלא את רשימות התצוגה ואת ההשפעה שלהן על האפליקציה, כדאי להבין גם איך Android מצייר תצוגות ללא האצת חומרה. בקטעים הבאים מתוארים מודלים של ציור מבוססי תוכנה ומודלים של ציור עם האצת חומרה.
מודל שרטוט מבוסס-תוכנה
במודל השרטוט של התוכנה, התצוגות נמשכות בשני השלבים הבאים:
- ביטול התוקף של ההיררכיה
- ציור ההיררכיה
בכל פעם שאפליקציה צריכה לעדכן חלק מממשק המשתמש שלה, היא מפעילה את הפונקציה invalidate() (או אחת מהגרסאות שלה) בכל תצוגה שהתוכן שלה השתנה. הודעות הביטול מועברות לאורך כל היררכיית התצוגה כדי לחשב את האזורים במסך שצריך לצייר מחדש (האזור המלוכלך). מערכת Android מציירת את כל התצוגות בהיררכיה שחופפות לאזור המלוכלך. לצערנו, יש שני חסרונות למודל הציור הזה:
קודם כל, המודל הזה דורש הפעלה של הרבה קוד בכל מעבר ציור. לדוגמה, אם האפליקציה שלך מפעילה את
invalidateבלחיצה על לחצן, והלחצן הזה ממוקם מעל תצוגה אחרת, מערכת Android מציירת מחדש את התצוגה גם אם היא לא השתנתה.הבעיה השנייה היא שמודל הציור יכול להסתיר באגים באפליקציה. מערכת Android מציירת מחדש תצוגות כשהן מצטלבות עם האזור המלוכלך, ולכן יכול להיות שתצוגה שהתוכן שלה השתנה תצויר מחדש גם אם לא בוצעה בה קריאה ל-
invalidate. במקרה כזה, אתם מסתמכים על ביטול תוקף של תצוגה אחרת כדי לקבל את ההתנהגות הנכונה. ההתנהגות הזו יכולה להשתנות בכל פעם שמשנים את האפליקציה. לכן, בכל פעם שמשנים נתונים או מצב שמשפיעים על קוד הציור של התצוגה, צריך לקרוא ל-invalidateבתצוגות המותאמות אישית.
מודל שרטוט מואץ באמצעות חומרה
מערכת Android עדיין משתמשת ב-invalidate וב-draw כדי לשלוח בקשות לעדכוני מסך ולעבד תצוגות, אבל היא מטפלת בציור בפועל בצורה שונה.
במקום להריץ את פקודות הציור באופן מיידי, מערכת Android מתעדת אותן ברשימות תצוגה, שמכילות את הפלט של קוד הציור של היררכיית התצוגה. אופטימיזציה נוספת היא שמערכת Android צריכה רק להקליט ולעדכן רשימות של תצוגות עבור תצוגות שסומנו כלא עדכניות על ידי קריאה ל-invalidate. אפשר לצייר מחדש צפיות שלא נפסלו על ידי שליחה מחדש של רשימת התצוגה שתועדה קודם. מודל הציור החדש
כולל שלושה שלבים:
ביטול התוקף של ההיררכיה
הקלטה ועדכון של רשימות התצוגה
ציור של רשימות התצוגה
במודל הזה, אי אפשר להסתמך על תצוגה שחוצה את האזור המלוכלך כדי שהשיטה draw תופעל. כדי לוודא שמערכת Android מקליטה את רשימת התצוגה של תצוגה, צריך להתקשר אל invalidate. אם לא עושים את זה, התצוגה נראית אותו הדבר גם אחרי שמשנים אותה.
שימוש ברשימות תצוגה משפר גם את הביצועים של אנימציות, כי כשמגדירים מאפיינים ספציפיים כמו שקיפות או סיבוב, לא צריך לבטל את התצוגה המטורגטת (הפעולה הזו מתבצעת אוטומטית). האופטימיזציה הזו חלה גם על תצוגות עם רשימות תצוגה (כל תצוגה כשהאפליקציה שלכם מואצת על ידי חומרה). לדוגמה, נניח שיש LinearLayout שמכיל ListView מעל Button. רשימת התצוגה של LinearLayout נראית כך:
DrawDisplayList(ListView)DrawDisplayList(Button)
נניח שרוצים לשנות את האטימות של ListView. אחרי הפעלת setAlpha(0.5f) ב-ListView, רשימת התצוגה כוללת עכשיו את הפריטים הבאים:
SaveLayerAlpha(0.5)DrawDisplayList(ListView)RestoreDrawDisplayList(Button)
קוד הציור המורכב של ListView לא הופעל. במקום זאת, המערכת עדכנה רק את רשימת התצוגה של LinearLayout, שהיא הרבה יותר פשוטה.
באפליקציה שבה לא מופעלת האצת חומרה, קוד הציור של הרשימה ושל הרכיב ההורה שלה מופעל מחדש.
תמיכה בפעולות שרטוט
כשמבצעים האצת חומרה, צינור העיבוד של רינדור דו-ממדי תומך בפעולות הציור הנפוצות ביותר של Canvas, וגם בפעולות רבות שפחות נפוצות. כל פעולות הציור שמשמשות לעיבוד אפליקציות שמגיעות עם Android, ווידג'טים ופריסות שמוגדרים כברירת מחדל ואפקטים חזותיים מתקדמים נפוצים כמו השתקפויות ומרקמים של אריחים נתמכים.
בטבלה הבאה מתואר רמת התמיכה בפעולות שונות ברמות API שונות:
| רמת ה-API הראשונה שנתמכת | ||||
| Canvas | ||||
| drawBitmapMesh() (colors array) | 18 | |||
| drawPicture() | 23 | |||
| drawPosText() | 16 | |||
| drawTextOnPath() | 16 | |||
| drawVertices() | 29 | |||
| setDrawFilter() | 16 | |||
| clipPath() | 18 | |||
| clipRegion() | 18 | |||
| clipRect(Region.Op.XOR) | 18 | |||
| clipRect(Region.Op.Difference) | 18 | |||
| clipRect(Region.Op.ReverseDifference) | 18 | |||
| clipRect() עם סיבוב או פרספקטיבה | 18 | |||
| צבע | ||||
| setAntiAlias() (לטקסט) | 18 | |||
| setAntiAlias() (לשורות) | 16 | |||
| setFilterBitmap() | 17 | |||
| setLinearText() | ✗ | |||
| setMaskFilter() | ✗ | |||
| setPathEffect() (לשורות) | 28 | |||
| setShadowLayer() (מלבד טקסט) | 28 | |||
| setStrokeCap() (עבור קווים) | 18 | |||
| setStrokeCap() (לנקודות) | 19 | |||
| setSubpixelText() | 28 | |||
| Xfermode | ||||
| PorterDuff.Mode.DARKEN (framebuffer) | 28 | |||
| PorterDuff.Mode.LIGHTEN (מקטע Frame buffer) | 28 | |||
| PorterDuff.Mode.OVERLAY (מקטע Frame buffer) | 28 | |||
| תוכנת הצללה (shader) | ||||
| ComposeShader בתוך ComposeShader | 28 | |||
| אותו סוג של הצללות בתוך ComposeShader | 28 | |||
| מטריצה מקומית ב-ComposeShader | 18 | |||
שינוי גודל התצוגה באזור העריכה
צינור העיבוד של רינדור דו-ממדי מואץ חומרה נבנה קודם כדי לתמוך בציור לא מותאם, כאשר חלק מפעולות הציור מורידות את האיכות באופן משמעותי בערכי קנה מידה גבוהים יותר. הפעולות האלה מיושמות כמרקמים שמצוירים בקנה מידה 1.0, ועוברים טרנספורמציה על ידי ה-GPU. החל מרמת API 28, כל פעולות הציור יכולות להתרחב ללא בעיה.
בטבלה הבאה אפשר לראות מתי ההטמעה שונתה כדי לטפל נכון בהיקפים גדולים:
| פעולת השרטוט שצריך לשנות את קנה המידה שלה | רמת ה-API הראשונה שנתמכת |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 28 |
| צורות פשוטות | 17 |
| צורות מורכבות | 28 |
| drawPath() | 28 |
| שכבת צל | 28 |
אם האפליקציה מושפעת מאחת מהתכונות החסרות או מההגבלות האלה, אפשר להשבית את האצת החומרה רק בחלק המושפע של האפליקציה על ידי קריאה ל-setLayerType(View.LAYER_TYPE_SOFTWARE, null).
כך תוכלו להמשיך ליהנות משיפור המהירות באמצעות חומרה בכל מקום אחר.
במאמר שליטה בהאצת חומרה מוסבר איך להפעיל ולהשבית את האצת החומרה ברמות שונות באפליקציה.
הצגת שכבות
בכל הגרסאות של Android, לתצוגות יש אפשרות לבצע רינדור לתוך מאגרי מידע מחוץ למסך, באמצעות מטמון הציור של התצוגה או באמצעות Canvas.saveLayer. למאגרי מידע או לשכבות מחוץ למסך יש כמה שימושים. אפשר להשתמש בהם כדי לשפר את הביצועים כשמנפישים תצוגות מורכבות, או כדי להחיל אפקטים של קומפוזיציה. לדוגמה, אפשר להטמיע אפקטים של הדהייה באמצעות התג Canvas.saveLayer כדי להציג תצוגה מפורטת בשכבה באופן זמני, ואז להרכיב אותה בחזרה על המסך עם גורם אטימות.
החל מ-Android 3.0 (רמת API 11), יש לכם יותר שליטה על האופן והזמן שבהם משתמשים בשכבות באמצעות השיטה View.setLayerType. ל-API הזה יש שני פרמטרים: סוג השכבה שרוצים להשתמש בה ואובייקט Paint אופציונלי שמתאר איך השכבה צריכה להיות מורכבת. אפשר להשתמש בפרמטר Paint כדי להחיל על שכבה מסנני צבע, מצבי מיזוג מיוחדים או שקיפות. תצוגה יכולה להשתמש באחד משלושת סוגי השכבות:
LAYER_TYPE_NONE: התצוגה מעובדת כרגיל ולא מגובה על ידי מאגר מחוץ למסך. זאת התנהגות ברירת המחדל.
LAYER_TYPE_HARDWARE: התצוגה מעובדת בחומרה לטקסטורה של חומרה אם האפליקציה מאיצה את הפעולה באמצעות חומרה. אם האפליקציה לא מאיצה את החומרה, סוג השכבה הזה מתנהג כמוLAYER_TYPE_SOFTWARE.
LAYER_TYPE_SOFTWARE: התצוגה מעובדת בתוכנה לביטמפה.
סוג השכבה שבה משתמשים תלוי ביעד:
ביצועים: שימוש בסוג של שכבת חומרה כדי לעבד תצוגה לתוך טקסטורה של חומרה. אחרי שתצוגה עוברת רינדור לשכבה, אין צורך להפעיל את קוד הציור שלה עד שהתצוגה קוראת ל-
invalidate. אחר כך אפשר להחיל ישירות על השכבה אנימציות מסוימות, כמו אנימציות אלפא, והפעולה הזו יעילה מאוד עבור ה-GPU.אפקטים חזותיים: אפשר להשתמש בשכבת חומרה או תוכנה וב
Paintכדי להחיל טיפולים ויזואליים מיוחדים על תצוגה. לדוגמה, אפשר לצייר תצוגה בשחור-לבן באמצעותColorMatrixColorFilter.תאימות: אפשר להשתמש בסוג שכבת תוכנה כדי לאלץ עיבוד של תצוגה בתוכנה. אם יש בעיות בעיבוד של תצוגה שמוגדרת להאצת חומרה (לדוגמה, אם כל האפליקציה מוגדרת להאצת חומרה), זו דרך קלה לעקוף את המגבלות של צינור העיבוד של החומרה.
הצגת שכבות ואנימציות
שכבות חומרה יכולות לספק אנימציות מהירות וחלקות יותר כשהאפליקציה מואצת באמצעות חומרה. יצירת אנימציה ב-60 פריימים לשנייה לא תמיד אפשרית כשמנפישים תצוגות מורכבות שמבצעות הרבה פעולות ציור. אפשר לפתור את הבעיה הזו באמצעות שכבות חומרה לעיבוד התצוגה לטקסטורה של חומרה. אחר כך אפשר להשתמש בטקסטורה של החומרה כדי להנפיש את התצוגה, וכך לא צריך לצייר מחדש את התצוגה כל הזמן בזמן ההנפשה. התצוגה לא מצוירת מחדש אלא אם משנים את המאפיינים שלה, ואז מתבצעת קריאה ל-invalidate, או אם מבצעים קריאה ל-invalidate באופן ידני. אם אתם מפעילים אנימציה באפליקציה ולא מקבלים את התוצאות הרצויות, כדאי להפעיל שכבות חומרה בתצוגות המונפשות.
כשמציגים תצוגה שנתמכת על ידי שכבת חומרה, חלק מהמאפיינים שלה מטופלים על ידי האופן שבו השכבה מורכבת על המסך. הגדרת המאפיינים האלה תהיה יעילה כי לא צריך לבטל את התוקף של התצוגה ולצייר אותה מחדש. בהמשך מפורטת רשימה של מאפיינים שמשפיעים על אופן השילוב של השכבה. קריאה לפונקציית ההגדרה של כל אחת מהמאפיינים האלה גורמת לביטול תוקף אופטימלי ולא לציור מחדש של התצוגה המטורגטת:
alpha: שינוי השקיפות של השכבה
x, y, translationX, translationY: שינוי המיקום של השכבה
scaleX,scaleY: שינוי הגודל של השכבה
rotation,rotationX,rotationY: שינוי הכיוון של השכבה במרחב תלת-ממדי
pivotX,pivotY: שינוי נקודת המוצא של הטרנספורמציות בשכבה
המאפיינים האלה הם השמות שמשמשים להנפשה של תצוגה באמצעות ObjectAnimator. כדי לגשת לנכסים האלה, צריך להפעיל את שיטת ה-setter או ה-getter המתאימה. לדוגמה, כדי לשנות את מאפיין האלפא, קוראים לפונקציה
setAlpha. בקטע הקוד הבא מוצגת הדרך הכי יעילה לסובב תצוגה בתלת-ממד סביב ציר ה-Y:
Kotlin
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).start()
Java
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start();
שכבות חומרה צורכות זיכרון וידאו, ולכן מומלץ מאוד להפעיל אותן רק למשך האנימציה ואז להשבית אותן אחרי שהאנימציה מסתיימת. אפשר לעשות את זה באמצעות מאזינים לאנימציה:
Kotlin
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { view.setLayerType(View.LAYER_TYPE_NONE, null) } }) start() }
Java
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animator.start();
מידע נוסף על אנימציה של מאפיינים זמין במאמר אנימציה של מאפיינים.
טיפים וטריקים
מעבר לגרפיקה דו-ממדית עם האצת חומרה יכול לשפר את הביצועים באופן מיידי, אבל עדיין כדאי לעצב את האפליקציה כך שתשתמש ב-GPU בצורה יעילה. לשם כך, מומלץ לפעול לפי ההמלצות הבאות:
- צמצום מספר התצוגות באפליקציה
- ככל שהמערכת תצטרך להפיק יותר צפיות, כך היא תפעל לאט יותר. הדבר נכון גם לגבי צינור העיבוד של התוכנה. הפחתת התצוגות היא אחת הדרכים הקלות ביותר לבצע אופטימיזציה של ממשק המשתמש.
- הימנעות ממשיכת יתר
- אל תציירו יותר מדי שכבות אחת על השנייה. מסירים תצוגות שמוסתרות לחלוטין על ידי תצוגות אטומות אחרות שמוצגות מעליהן. אם אתם צריכים לצייר כמה שכבות שמשולבות אחת על השנייה, כדאי למזג אותן לשכבה אחת. כלל אצבע טוב לשימוש בחומרה הנוכחית הוא לא לצייר יותר מפי 2.5 ממספר הפיקסלים במסך לכל פריים (פיקסלים שקופים במפת סיביות נספרים!).
- לא ליצור אובייקטים של עיבוד בשיטות ציור
- טעות נפוצה היא ליצור
Paintחדש אוPathחדש בכל פעם שמפעילים שיטת עיבוד. כך מאלצים את איסוף האשפה לפעול בתדירות גבוהה יותר, ועוקפים גם את המטמון ואת האופטימיזציות בצינור העברת הנתונים של החומרה. - לא לשנות את הצורות לעיתים קרובות מדי
- צורות מורכבות, נתיבים ומעגלים, לדוגמה, מעובדים באמצעות מסכות טקסטורה. בכל פעם שיוצרים או משנים נתיב, צינור העיבוד של החומרה יוצר מסכה חדשה, וזה עלול להיות יקר.
- לא לשנות מפות סיביות לעיתים קרובות מדי
- בכל פעם שמשנים את התוכן של מפת סיביות, הוא מועלה שוב כמרקם GPU בפעם הבאה שמציירים אותו.
- חשוב להשתמש בגרסת אלפא בזהירות
- כשיוצרים תצוגה שקופה באמצעות
setAlpha,AlphaAnimationאוObjectAnimator, היא מעובדת במאגר מחוץ למסך, מה שמכפיל את קצב המילוי הנדרש. כשמחילים את המאפיין alpha על תצוגות גדולות מאוד, כדאי להגדיר את סוג השכבה של התצוגה ל-LAYER_TYPE_HARDWARE.