ממשקי ה-API של android.media.projection
, שהוצגו ב-Android 5 (רמת API 21), מאפשרים לצלם את התוכן של מסך המכשיר כסטרימינג של מדיה, שאפשר להפעיל, להקליט או להעביר למכשירים אחרים, כמו טלוויזיות.
ב-Android 14 (רמת API 34) נוספה תכונת שיתוף מסך של אפליקציה, שמאפשרת למשתמשים לשתף חלון של אפליקציה אחת במקום את כל מסך המכשיר, ללא קשר למצב החלון. כשמשתפים מסך של אפליקציה, שורת הסטטוס, סרגל הניווט, ההתראות ורכיבים אחרים של ממשק המשתמש של המערכת לא מוצגים במסך המשותף – גם אם משתמשים בשיתוף המסך של האפליקציה כדי לצלם את האפליקציה במסך מלא. רק התוכן של האפליקציה שנבחרה ישותף.
שיתוף מסך האפליקציה מבטיח את פרטיות המשתמשים, מגביר את הפרודוקטיביות שלהם ומאפשר להם לבצע כמה משימות בו-זמנית. לשם כך, המשתמשים יכולים להפעיל כמה אפליקציות אבל להגביל את שיתוף התוכן לאפליקציה אחת בלבד.
שלושה ייצוגים של תצוגה
הקרנת מדיה מתעדת את התוכן של מסך המכשיר או חלון האפליקציה, ולאחר מכן מקרינה את התמונה שצולמה על מסך וירטואלי שמריץ את התמונה ב-Surface
.
האפליקציה מספקת את Surface
באמצעות MediaRecorder
, SurfaceTexture
או ImageReader
, שמשתמשים בתוכן של המסך שצולם ומאפשרים לנהל תמונות שעברן רינדור ב-Surface
בזמן אמת. אפשר לשמור את התמונות כקלטת או להעביר אותן לטלוויזיה או למכשיר אחר.
תצוגה אמיתית
כדי להתחיל סשן של הקרנת מדיה, צריך לקבל אסימון שמעניק לאפליקציה את היכולת לתעד את התוכן של מסך המכשיר או חלון האפליקציה. האסימון מיוצג על ידי מופע של הכיתה MediaProjection
.
משתמשים בשיטה getMediaProjection()
של שירות המערכת MediaProjectionManager
כדי ליצור מכונה של MediaProjection
כשמתחילים פעילות חדשה. מתחילים את הפעילות עם כוונה (intent) מהשיטה createScreenCaptureIntent()
כדי לציין פעולת צילום מסך:
Kotlin
val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java) var mediaProjection : MediaProjection
val startMediaProjection = registerForActivityResult( StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) } }
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())
Java
final MediaProjectionManager mediaProjectionManager = getSystemService(MediaProjectionManager.class); final MediaProjection[] mediaProjection = new MediaProjection[1];
ActivityResultLauncherstartMediaProjection = registerForActivityResult( new StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { mediaProjection[0] = mediaProjectionManager .getMediaProjection(result.getResultCode(), result.getData()); } } );
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());
מסך וירטואלי
הרכיב המרכזי של הקרנת מדיה הוא המסך הווירטואלי, שיוצרים באמצעות קריאה ל-createVirtualDisplay()
במכונה של MediaProjection
:
Kotlin
virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenCapture", width, height, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null)
Java
virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenCapture", width, height, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null);
הפרמטרים width
ו-height
מציינים את המאפיינים של המסך הווירטואלי. כדי לקבל ערכים לרוחב ולגובה, משתמשים בממשקי ה-API של WindowMetrics
שהוצגו ב-Android 11 (רמת API 30). (פרטים נוספים זמינים בקטע גודל הקרנת המדיה).
Surface
מגדירים את גודל משטח הקרנת המדיה כך שיפיק פלט ברזולוציה המתאימה. כדאי להגדיל את המשטח (רזולוציה נמוכה) כדי להעביר את המסך לטלוויזיות או למסכי מחשב, ולהקטין אותו (רזולוציה גבוהה) כדי לצלם את המסך של המכשיר.
החל מ-Android 12L (API ברמה 32), כשהמערכת מבצעת עיבוד (רנדור) של תוכן שצולם על פני השטח, היא משנה את הגודל של התוכן באופן אחיד תוך שמירה על יחס הגובה-רוחב, כך ששני המימדים של התוכן (רוחב וגובה) יהיו שווים או קטנים מהמימדים התואמים של פני השטח. לאחר מכן, התוכן שצילמתם ימורכז על פני השטח.
הגישה של Android 12L לשינוי קנה המידה משפרת את ההעברה של המסך לטלוויזיות ולמסכים גדולים אחרים, על ידי הגדלת התמונה על המסך תוך שמירה על יחס הגובה-רוחב הנכון.
הרשאה לשימוש בשירות שפועל בחזית
אם האפליקציה שלכם מטרגטת ל-Android 14 ואילך, מניפסט האפליקציה צריך לכלול הצהרת הרשאה לסוג השירות שפועל בחזית mediaProjection
:
<manifest ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<application ...>
<service
android:name=".MyMediaProjectionService"
android:foregroundServiceType="mediaProjection"
android:exported="false">
</service>
</application>
</manifest>
מפעילים את שירות הקרנת המדיה באמצעות קריאה ל-startForeground()
.
אם לא מציינים את סוג השירות שפועל בחזית בקריאה, ברירת המחדל של הסוג היא מספר שלם בייטבי של סוגי השירותים שפועלים בחזית שהוגדרו במניפסט. אם לא צוינו סוגי שירותים במניפסט, המערכת תשליך את השגיאה MissingForegroundServiceTypeException
.
הסכמת המשתמשים
האפליקציה חייבת לבקש מהמשתמשים הסכמה לפני כל סשן הקרנת מדיה. סשן הוא קריאה יחידה ל-createVirtualDisplay()
. צריך להשתמש באסימון MediaProjection
רק פעם אחת כדי לבצע את הקריאה.
ב-Android 14 ואילך, השיטה createVirtualDisplay()
גורמת להשלכת SecurityException
אם האפליקציה מבצעת אחת מהפעולות הבאות:
- העברת מופע
Intent
שהוחזר מ-createScreenCaptureIntent()
ל-getMediaProjection()
יותר מפעם אחת - קריאות ל-
createVirtualDisplay()
יותר מפעם אחת באותו מופעMediaProjection
גודל הקרנת המדיה
הקרנת מדיה יכולה לתעד את כל מסך המכשיר או חלון של אפליקציה, ללא קשר למצב החלון.
גודל ראשוני
כשמשתמשים בהקרנת מדיה במסך מלא, האפליקציה צריכה לקבוע את גודל המסך של המכשיר. כשמשתפים מסך מאפליקציה, האפליקציה לא תוכל לקבוע את גודל המסך שצולם עד שהמשתמש יבחר את אזור הצילום. לכן, הגודל הראשוני של כל הקרנת מדיה הוא הגודל של מסך המכשיר.
משתמשים בשיטה WindowManager
getMaximumWindowMetrics()
של הפלטפורמה כדי להחזיר אובייקט WindowMetrics
של מסך המכשיר, גם אם אפליקציית המארח של הקרנת המדיה נמצאת במצב חלונות מרובים ותופסת רק חלק מהמסך.
כדי לשמור על תאימות עד לרמת API 14, צריך להשתמש ב-method WindowMetricsCalculator
computeMaximumWindowMetrics()
מהספרייה WindowManager
של Jetpack.
קוראים ל-method WindowMetrics
getBounds()
כדי לקבל את הרוחב והגובה של המסך במכשיר.
שינויים בגודל
הגודל של הקרנת המדיה יכול להשתנות כשמסובבים את המכשיר או כשהמשתמש בוחר חלון של אפליקציה כאזור הצילום בזמן שיתוף המסך של האפליקציה. יכול להיות שפרויקציית המדיה תהיה בפורמט letterbox אם התוכן שצולם הוא בגודל שונה ממדדי החלון המקסימלי שהתקבלו כשהגדרתם את פרויקציית המדיה.
כדי לוודא שהקרנת המדיה תואמת במדויק לגודל התוכן שצולם בכל אזור שצולם ובכל סיבוב של המכשיר, צריך להשתמש ב-callback onCapturedContentResize()
כדי לשנות את הגודל של הצילום. (למידע נוסף, עיינו בקטע התאמה אישית שבהמשך).
התאמה אישית
האפליקציה יכולה להתאים אישית את חוויית המשתמש בהקרנת המדיה באמצעות ממשקי ה-API הבאים של MediaProjection.Callback
:
onCapturedContentVisibilityChanged()
: מאפשרת לאפליקציית המארח (האפליקציה שהפעילה את הקרנת המדיה) להציג או להסתיר את התוכן המשותף.אפשר להשתמש בקריאה החוזרת הזו כדי להתאים אישית את ממשק המשתמש של האפליקציה על סמך העובדה שהאזור שצולם גלוי למשתמש. לדוגמה, אם האפליקציה שלכם גלויה למשתמש והיא מציגה את התוכן שצולם בממשק המשתמש של האפליקציה, והאפליקציה שצולמה גלויה גם למשתמש (כפי שמצוין באמצעות קריאה חוזרת (callback) הזו), המשתמש רואה את אותו תוכן פעמיים. אפשר להשתמש בקריאה החוזרת כדי לעדכן את ממשק המשתמש של האפליקציה ולהסתיר את התוכן שצולם, וכך לפנות מקום בפריסת האפליקציה לתוכן אחר.
onCapturedContentResize()
: מאפשר לאפליקציית המארח לשנות את הגודל של הקרנת המדיה במסך הווירטואלי ושל הקרנת המדיהSurface
על סמך גודל אזור התצוגה שצולם.האירוע מופעל בכל פעם שהתוכן שצולם – חלון של אפליקציה אחת או מסך המכשיר במלואו – משתנה בגודל (בגלל סיבוב המכשיר או מעבר של האפליקציה שצולמה למצב חלון אחר). אפשר להשתמש ב-API הזה כדי לשנות את הגודל של המסך הווירטואלי ושל המשטח, כדי לוודא שיחס הגובה-רוחב תואם לתוכן שמצולם ושלא מוצגת תמונת letterbox.
שחזור משאבים
האפליקציה צריכה לרשום את פונקציית ה-call back MediaProjection
onStop()
כדי לקבל הודעה כשסשן הקרנת המדיה מושהה ולא חוקי יותר. כשהסשן מושהה, האפליקציה צריכה לשחרר את המשאבים שהיא מחזיקה, כמו המסך הווירטואלי ופלטפורמת ההקרנה. סשן הקרנת מדיה מושהה לא יכול ליצור יותר מסך וירטואלי חדש, גם אם האפליקציה לא יצרה בעבר מסך וירטואלי להקרנת המדיה הזו.
המערכת מפעילה את קריאת החזרה (callback) כשהקרנת המדיה מסתיימת. הסיבות לכך יכולות להיות, בין היתר:
- המשתמש מפסיק את הסשן באמצעות ממשק המשתמש של האפליקציה או צ'יפ של שורת הסטטוס של הקרנת המדיה במערכת
- המסך נעול
- התחלת סשן אחר להקרנת מדיה
- תהליך האפליקציה הושבת
אם האפליקציה לא תירשם את הקריאה החוזרת, כל קריאה ל-createVirtualDisplay()
תגרום להשלכת IllegalStateException
.
ביטול ההסכמה
ב-Android מגרסה 14 ואילך, שיתוף המסך של האפליקציה מופעל כברירת מחדל. בכל סשן הקרנת מדיה, המשתמשים יכולים לשתף חלון של אפליקציה או את המסך כולו.
כדי לבטל את ההסכמה לשיתוף המסך של האפליקציה, אפשר להפעיל את השיטה createScreenCaptureIntent(MediaProjectionConfig)
עם הארגומנט MediaProjectionConfig
שמוחזר מהקריאה ל-createConfigForDefaultDisplay()
.
קריאה ל-createScreenCaptureIntent(MediaProjectionConfig)
עם ארגומנט MediaProjectionConfig
שמוחזר מקריאה ל-createConfigForUserChoice()
זהה להתנהגות ברירת המחדל, כלומר קריאה ל-createScreenCaptureIntent()
.
אפליקציות שניתן לשנות את הגודל שלהן
תמיד צריך לוודא שאפליקציות הקרנת המדיה ניתנות לשינוי גודל (resizeableActivity="true"
). אפליקציות שניתן לשנות את הגודל שלהן תומכות בשינויים בהגדרות המכשיר ובמצב 'חלונות מרובים' (ראו תמיכה במצב 'חלונות מרובים').
אם לא ניתן לשנות את הגודל של האפליקציה, היא צריכה לשלוח שאילתה לגבי גבולות התצוגה מההקשר של החלון ולהשתמש ב-getMaximumWindowMetrics()
כדי לאחזר את WindowMetrics
של אזור התצוגה המקסימלי שזמין לאפליקציה :
Kotlin
val windowContext = context.createWindowContext(context.display!!, WindowManager.LayoutParams.TYPE_APPLICATION, null) val projectionMetrics = windowContext.getSystemService(WindowManager::class.java) .maximumWindowMetrics
Java
Context windowContext = context.createWindowContext(context.getDisplay(), WindowManager.LayoutParams.TYPE_APPLICATION, null); WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class) .getMaximumWindowMetrics();
צ'יפ בשורת הסטטוס ועצירה אוטומטית
נקודות חולשה של הקרנת מסך חושפות נתונים פרטיים של משתמשים, כמו מידע פיננסי, כי המשתמשים לא מבינים שהמסך של המכשיר שלהם משותף.
ב-Android מגרסה 15 ואילך (רמת API 35 ואילך) מוצג צ'יפ גדול ובולט בסרגל הסטטוס כדי להתריע למשתמשים על הקרנת מסך מתמשכת. המשתמשים יכולים להקיש על הצ'יפ כדי להפסיק את השיתוף, ההעברה או ההקלטה של המסך. בנוסף, הקרנת המסך תיפסק באופן אוטומטי כשמסך המכשיר יינעל.
כדי לבדוק את הזמינות של צ'יפ סרגל הסטטוס של הקרנת המדיה, מתחילים לשתף את המסך, לבצע העברה או להקליט. הצ'יפ אמור להופיע בשורת הסטטוס.
כדי לוודא שהאפליקציה משחררת משאבים ומעדכנת את ממשק המשתמש שלה כשהקרנת המסך מופסקת כתוצאה מאינטראקציה של המשתמש עם צ'יפ שורת המצב או הפעלה של מסך הנעילה, צריך לבצע את הפעולות הבאות:
יוצרים מכונה של
MediaProjection.Callback
.מטמיעים את שיטת הקריאה החוזרת
onStop()
. ה-method נקרא כשהקרנת המסך נעצרת. משחררים את המשאבים שהאפליקציה שומרת ומעדכנים את ממשק המשתמש של האפליקציה לפי הצורך.
כדי לבדוק את הקריאה החוזרת, מקישים על הצ'יפ בשורת הסטטוס או נועלים את מסך המכשיר כדי להפסיק את הקרנת המסך. מוודאים שהשיטה onStop()
נקראת והאפליקציה מגיבה כמצופה.
מקורות מידע נוספים
מידע נוסף על הקרנת מדיה זמין במאמר צילום של הפעלת וידאו ואודיו.