שיפור מהירות באמצעות חומרה

החל מ-Android 3.0 (רמת API 11), צינור עיבוד הנתונים של Android 2D לעיבוד תומך בחומרה היא האצה, כלומר כל פעולות השרטוט שמבוצעות באזור העריכה של View משתמשים ב-GPU. לאור כמות המשאבים המוגברת שנדרשים כדי להפעיל את התכונה שיפור המהירות באמצעות חומרה, האפליקציה שלך תצרוך יותר RAM.

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

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

קראו גם את המאמר OpenGL עם ממשקי ה-API של Framework. ו-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);

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

איך לקבוע אם התצוגה תואץ באמצעות חומרה

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

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

אם אתם חייבים לבצע את הבדיקה הזו בקוד השרטוט, השתמשו ב- Canvas.isHardwareAccelerated() במקום זאת של View.isHardwareAccelerated() כשהדבר אפשרי. כאשר צפייה מצורף לחלון עם שיפור חומרה, עדיין ניתן לצייר אותו באמצעות רכיב שאינו חומרה קנבס מואץ. זה קורה, למשל, כשמציירים תצוגה למפת סיביות (bitmap) לשמירה במטמון למטרות.

מודלים לשרטוט ב-Android

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

מודל שרטוט מבוסס-תוכנה

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

  1. ביטול ההיררכיה
  2. ציירו את ההיררכיה

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

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

הערה: בתצוגות ב-Android, המערכת מבצעת באופן אוטומטי קריאה אוטומטית למספר invalidate() כשהמאפיינים שלהן משתנים, למשל הרקע או הטקסט ב-TextView.

מודל שרטוט מהירות באמצעות חומרה

מערכת Android עדיין משתמשת ב-invalidate() וב-draw() כדי לבקש עדכוני מסך ולעבד תצוגות, אבל מטפלת או לצייר בפועל. במקום להריץ את פקודות השרטוט באופן מיידי, מתעדת אותן ברשימות תצוגה, שמכילות את הפלט של באמצעות קוד שרטוט. אופטימיזציה נוספת היא שמערכת Android צריכה רק להקליט ולעדכן הצגת רשימות עבור תצוגות שסומנו כ'מלוכלך' על ידי invalidate() שיחה. אפשר לשרטט מחדש תצוגות שלא בוטלו על ידי הנפקה מחדש של רשימה מוקלטת של תצוגה. מודל השרטוט החדש מכיל שלושה שלבים:

  1. ביטול ההיררכיה
  2. הקלטה ועדכון של רשימות בתצוגה
  3. שרטוט של רשימות התצוגה

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

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

  • DrawDisplayList(ListView)
  • DrawDisplayList(לחצן)

עכשיו נניח שאתם רוצים לשנות את האטימות של ListView. אחרי מפעיל את setAlpha(0.5f) ב-ListView, רשימת התצוגה עכשיו מכיל/ה את זה:

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • שחזור
  • DrawDisplayList(לחצן)

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

תמיכה בפעולות שרטוט

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

בטבלה הבאה מתוארת רמת התמיכה בפעולות השונות ברמות ה-API:

רמת ה-API הראשונה שנתמכת
משטח ציור
pullBitmapMesh() (מערך צבעים) 18
pullPicture() 23
pullPosText() 16
DoodleTextOnPath() 16
pullVertices() 29
setDrawFilter() 16
ClipPath() 18
ClipRegion() 18
ClipRect(Region.Op.XOR) 18
ClipRect(Region.Op.Compare) 18
ClipRect(Region.Op.Reverse סיבוב) 18
ClipRect() עם רוטציה/פרספקטיבה 18
צבע
setAantiAlias() (לטקסט) 18
setAantiAlias() (לשורות) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathPath() (לקווים) 28
setShadowLayer() (מלבד טקסט) 28
setStrokeCap() (לקווים) 18
setStrokeCap() (לנקודות) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.lightEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
וילון גלילה
ComposeShader בתוך ComposeShader 28
תוכנות הצללה (shader) מאותו סוג בתוך ComposeShader 28
מטריצה מקומית ב-ComposeShader 18

שינוי גודל הקנבס

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

בטבלה הבאה אפשר לראות מתי ההטמעה שונתה כדי לטפל כראוי בהיקפים גדולים:
פעולת שרטוט להתאמה לעומס (scaling) רמת ה-API הראשונה שנתמכת
pullText() 18
pullPosText() 28
DoodleTextOnPath() 28
צורות פשוטות* 17
צורות מורכבות* 28
pullPath() 28
שכבת הצללה 28

הערה: 'פשוט' הצורות הן drawRect(), drawCircle(), drawOval(), drawRoundRect() וגם הפקודות drawArc() (עם useCenter=false) שהונפקו דרך צבע שאין לו PathImpact, ולא מכיל שאילתות איחוד (join) שלא מוגדרות כברירת מחדל (באמצעות setStrokeJoin() /) setStrokeMiter()). מקרים אחרים של פקודות ציור כאלה נמצאים בקטע 'Complex', באזור מהתרשים שלמעלה.

אם האפליקציה שלכם מושפעת מאחת מהתכונות או מהמגבלות החסרות האלה, תוכלו להפעיל את האצת החומרה רק לחלק המושפע של האפליקציה 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: התצוגה מעובדת בתוכנה מפת סיביות (bitmap).

סוג השכבה שבו תשתמשו תלוי ביעד שלכם:

  • ביצועים: שימוש בסוג שכבת חומרה כדי לעבד תצוגה לחומרה טקסטורה. אחרי עיבוד של תצוגה לשכבה, אין צורך להפעיל את קוד השרטוט שלה עד שהתצוגה תקרא invalidate(). אנימציות מסוימות, כמו את אנימציות האלפא, ואז אפשר להחיל אותן ישירות על השכבה, פעולה שה-GPU יכול לעשות.
  • אפקטים חזותיים: צריך להשתמש בסוג שכבת חומרה או תוכנה וב-Paint כדי להחיל טיפולים חזותיים מיוחדים בצפייה. לדוגמה: שרטוט של תצוגה בשחור-לבן באמצעות ColorMatrixColorFilter.
  • תאימות: שימוש בסוג של שכבת תוכנה כדי לאלץ עיבוד של תצוגה תוכנה. אם בתצוגה עם שיפור מהירות באמצעות חומרה (לדוגמה, אם האפליקציה מופעלת באמצעות חומרה), יש בעיות בעיבוד, זו דרך קלה לעבוד בנושא מגבלות של עיבוד החומרה צינור עיבוד נתונים.

הצגת שכבות ואנימציות

שכבות חומרה יכולות לספק אנימציות מהירות וחלקות יותר כאשר האפליקציה מואץ באמצעות חומרה. לא תמיד ניתן להריץ אנימציה בקצב של 60FPS ליצור אנימציות של תצוגות מורכבות שמבצעות הרבה פעולות שרטוט. ניתן להקל על הפעולה הזו באמצעות שכבות חומרה כדי לעבד את התצוגה למרקם חומרה. טקסטורת החומרה כך שניתן יהיה להשתמש בו כדי להנפיש את התצוגה, כך שאין צורך שהתצוגה תוכל לצייר עצמה כל הזמן כשמתבצעת אנימציה. התצוגה לא תצויין מחדש אלא אם תשנו את נכסים, שקורא ל-invalidate(), או אם קוראים ל-invalidate() באופן ידני. אם מפעילים אנימציה את האפליקציה שלך ולא לקבל את התוצאות החלקות שרצית, כדאי לשקול להפעיל שכבות חומרה לצפיות המונפשות.

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

  • alpha: שינוי מידת האטימות של השכבה
  • x, y, translationX, translationY: שינוי המיקום של השכבה
  • scaleX, scaleY: שינוי גודל השכבה
  • rotation, rotationX, rotationY: משנה את כיוון השכבה במרחב תלת-ממדי
  • pivotX, pivotY: שינוי מקור הטרנספורמציות של השכבה

המאפיינים האלה הם השמות שמשמשים ליצירת אנימציה של תצוגה באמצעות ObjectAnimator. כדי לגשת לנכסים האלו, קוראים לפונקציה או מקודד-מפענח. לדוגמה, כדי לשנות את מאפיין האלפא, קוראים לפונקציה 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 ממספר הפיקסלים על המסך בכל פריים (פיקסלים שקופים בספירת מפת סיביות (bitmap)!).
לא ליצור אובייקטים לעיבוד מידע בשיטות שרטוט
טעות נפוצה היא ליצור Paint או Path חדש בכל פעם שמופעלת שיטת רינדור. הפעולה הזאת גורמת לאשפה והם ירוצו בתדירות גבוהה יותר וגם עוקפים את המטמון והאופטימיזציות בחומרה צינור עיבוד נתונים.
לא לשנות צורות לעיתים קרובות מדי
למשל, צורות מורכבות, נתיבים ומעגלים מעובדים באמצעות מסכות טקסטורה. הכול כשיוצרים או משנים נתיב, צינור עיבוד הנתונים של החומרה יוצר מסכה חדשה, יקרות.
אל תשנו את מפות סיביות בתדירות גבוהה מדי
בכל פעם שמשנים את התוכן של מפת סיביות, היא מועלה שוב כמרקם של GPU בפעם הבאה שתציירו אותו.
חשוב להשתמש באלפא בזהירות
כשהופכים את התצוגה לשקופה באמצעות setAlpha(), AlphaAnimation או ObjectAnimator, עובר מאגר נתונים זמני מחוץ למסך, שמכפיל את קצב המילוי הנדרש. בהחלת אלפא בתצוגות גדולות מאוד, כדאי להגדיר את סוג השכבה של התצוגה LAYER_TYPE_HARDWARE