SurfaceView ו-GLSurfaceView

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

SurfaceView

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

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

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

הצגה באמצעות SurfaceView מועילה במקרים שבהם צריך להציג על משטח נפרד, למשל כשמציגים באמצעות Camera API או הקשר OpenGL ES. כשמבצעים רינדור באמצעות SurfaceView, ‏ SurfaceFlinger מרכיב ישירות את המאגרים במסך. בלי SurfaceView, צריך ליצור קומפוזיציה של מאגרי נתונים למשטח מחוץ למסך, ואז ליצור קומפוזיציה של המשטח למסך, כך שהרנדור עם SurfaceView מבטל עבודה מיותרת. אחרי שהרכיב מוצג באמצעות SurfaceView, משתמשים ב-UI thread כדי לתאם עם מחזור החיים של הפעילות ולבצע התאמות לגודל או למיקום של התצוגה אם צריך. לאחר מכן, ה-Hardware Composer משלב את ממשק המשתמש של האפליקציה ואת השכבות האחרות.

המשטח החדש הוא הצד של היצרן ב-BufferQueue, והצרכן שלו הוא שכבת SurfaceFlinger. אפשר לעדכן את השטח באמצעות כל מנגנון שיכול להזין BufferQueue, כמו פונקציות Canvas שסופקו על ידי השטח, צירוף של EGLSurface וציור על השטח באמצעות GLES, או הגדרה של מפענח מדיה לכתיבה של השטח.

‫SurfaceView ומחזור החיים של הפעילות

כשמשתמשים ב-SurfaceView, צריך לעבד את ה-Surface משרשור שאינו שרשור ה-UI הראשי.

לפעילות עם SurfaceView יש שני אוטומטים נפרדים אבל תלויים זה בזה:

  • אפליקציה onCreate/onResume/onPause
  • נוצר/השתנה/נמחק משטח

כשהפעילות מתחילה, מתקבלות קריאות חוזרות בסדר הזה:

  1. onCreate()
  2. onResume()
  3. surfaceCreated()
  4. surfaceChanged()

אם לוחצים על 'הקודם', מקבלים:

  1. onPause()
  2. surfaceDestroyed() (התקשרתי רגע לפני שהמשטח נעלם)

אם מסובבים את המסך, הפעילות נהרסת ונוצרת מחדש, ואתם מקבלים את המחזור המלא. כדי לדעת אם מדובר בהפעלה מחדש מהירה, בודקים את isFinishing(). יכול להיות שתתחילו או תפסיקו פעילות כל כך מהר ש-surfaceCreated() יקרה אחרי onPause().

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

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

התחלה והפסקה של השרשור עם התחלה והפסקה של הפעילות פועלות היטב עם מחזור החיים של האפליקציה. מתחילים את השרשור של הרינדור ב-onResume() ומפסיקים אותו ב-onStop(). כשיוצרים את השרשור ומגדירים אותו, לפעמים המשטח כבר קיים ולפעמים לא (למשל, הוא עדיין פעיל אחרי שמחליפים את המסך באמצעות לחצן ההפעלה). צריך לחכות ליצירת השטח לפני שמפעילים את האתחול בשרשור. אי אפשר להפעיל את הפונקציה ב-callback של surfaceCreate() כי היא לא תופעל שוב אם המשטח לא נוצר מחדש. במקום זאת, צריך לשלוח שאילתה או לשמור במטמון את מצב המשטח ולהעביר אותו לשרשור של הרכיב הגרפי.

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

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

GLSurfaceView

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

לדוגמה, GLSurfaceView יוצר שרשור לעיבוד ומגדיר שם הקשר EGL. המצב מנוקה באופן אוטומטי כשהפעילות מושהית. רוב האפליקציות לא צריכות לדעת דבר על EGL כדי להשתמש ב-GLES עם GLSurfaceView.

ברוב המקרים, GLSurfaceView יכול להקל על העבודה עם GLES. במצבים מסוימים, הוא עלול להפריע.