חדשות על מוצרים
שיפור הפעלת המדיה: ניתוח מעמיק של PreloadManager ב-Media3 – חלק 2
משך הקריאה: 9 דקות
ברוכים הבאים לחלק השני בסדרה בת שלושה חלקים בנושא טעינה מראש של מדיה באמצעות Media3. הסדרה הזו נועדה להדריך אתכם בתהליך של יצירת חוויות מדיה עם זמן תגובה מהיר וזמן אחזור נמוך באפליקציות ל-Android.
- חלק 1: מבוא לטעינה מראש באמצעות Media3 כולל את היסודות. בדקנו את ההבדל בין PreloadConfiguration לפלייליסטים פשוטים לבין DefaultPreloadManager, שהוא כלי חזק יותר לממשקי משתמש דינמיים. למדתם איך להטמיע את מחזור החיים הבסיסי של ה-API: הוספת מדיה באמצעות add(), אחזור של MediaSource מוכן באמצעות getMediaSource(), ניהול של סדרי עדיפויות באמצעות setCurrentPlayingIndex() ו-invalidate(), ושחרור משאבים באמצעות remove() ו-release().
- חלק 2 (הפוסט הזה): בפוסט הזה בבלוג נסביר על היכולות המתקדמות של DefaultPreloadManager. במאמר אנחנו מסבירים איך להפיק תובנות באמצעות PreloadManagerListener, איך להטמיע שיטות מומלצות שמוכנות להטמעה בסביבת ייצור כמו שיתוף רכיבי ליבה עם ExoPlayer, ואיך לשלוט בדפוס החלון הנע כדי לנהל את הזיכרון בצורה יעילה.
- חלק 3: בחלק האחרון בסדרה הזו נסביר איך לשלב את PreloadManager עם מטמון דיסק קבוע, כדי שתוכלו לצמצם את צריכת הנתונים באמצעות ניהול משאבים ולספק חוויה חלקה.
אם אתם חדשים בנושא טעינה מראש ב-Media3, מומלץ מאוד לקרוא את חלק 1 לפני שממשיכים. אם אתם רוצים ללכת מעבר לבסיס, נסביר איך לשפר את ההטמעה של הפעלת מדיה.
האזנה: אחזור ניתוח נתונים באמצעות PreloadManagerListener
כשרוצים להשיק תכונה בסביבת ייצור, מפתחי אפליקציות רוצים גם להבין את הניתוח שמאחורי התכונה ולתעד אותו. איך אפשר לוודא שאסטרטגיית הטעינה מראש יעילה בסביבה של העולם האמיתי? כדי לענות על השאלה הזו, צריך נתונים על שיעורי הצלחה, כשלים וביצועים. הממשק PreloadManagerListener הוא המנגנון העיקרי לאיסוף הנתונים האלה.
ה-PreloadManagerListener מספק שתי פונקציות קריטיות של Callback, שמאפשרות לקבל תובנות חשובות לגבי תהליך הטעינה מראש והסטטוס שלו.
- onCompleted(MediaItem mediaItem): הקריאה החוזרת הזו מופעלת אחרי השלמה מוצלחת של בקשת טעינה מראש, כפי שהוגדרה על ידי TargetPreloadStatusControl.
- onError(שגיאת PreloadException): הקריאה החוזרת הזו יכולה להיות שימושית לניפוי באגים ולמעקב. הוא מופעל כשמתרחשת שגיאה בטעינה מראש, ומספק את החריגה המשויכת.
אפשר לרשום מאזין באמצעות הפעלת method אחת, כמו בדוגמת הקוד הבאה:
val preloadManagerListener = object : PreloadManagerListener {
override fun onCompleted(mediaItem: MediaItem) {
// Log success for analytics.
Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
}
override fun onError( preloadError: PreloadException) {
// Log the specific error for debugging and monitoring.
Log.e("PreloadAnalytics", "Preload error ", preloadError)
}
}
preloadManager.addListener(preloadManagerListener)
הפקת תובנות מהמאזין
אפשר לקשר את פונקציות ה-callback האלה של מאזינים לצינור ניתוח הנתונים שלכם. העברת האירועים האלה למנוע לניתוח נתונים מאפשרת לענות על שאלות מרכזיות כמו:
- מהו שיעור ההצלחה של הטעינה מראש? (היחס בין אירועי onCompleted לבין סך כל הניסיונות לטעינה מראש)
- אילו רשתות CDN או פורמטים של סרטונים מציגים את שיעורי השגיאות הגבוהים ביותר? (על ידי ניתוח החריגים מ-onError)
- מהו שיעור השגיאות בטעינה מראש? (היחס בין אירועי onError לבין סך כל ניסיונות הטעינה מראש)
הנתונים האלה יכולים לספק לכם משוב כמותי על אסטרטגיית הטעינה מראש, ולאפשר לכם לבצע בדיקות A/B ולשפר את חוויית המשתמש על סמך נתונים. הנתונים האלה יכולים לעזור לכם לכוונן בצורה חכמה את משכי הטעינה מראש, את מספר הסרטונים שאתם רוצים לטעון מראש ואת מאגרי הנתונים הזמניים שאתם מקצים.
מעבר לניפוי באגים: שימוש ב-onError לחזרה מסודרת לממשק משתמש
טעינה מראש שנכשלה היא אינדיקטור חזק לאירוע של אחסון בזיכרון המטמון שצפוי למשתמש. הקריאה החוזרת (callback) של onError מאפשרת לכם להגיב באופן ריאקטיבי. במקום רק לרשום את השגיאה ביומן, אפשר להתאים את ממשק המשתמש. לדוגמה, אם הטעינה מראש של הסרטון הבא נכשלת, האפליקציה יכולה להשבית את ההפעלה האוטומטית בהחלקה הבאה, ולדרוש מהמשתמש להקיש כדי להתחיל את ההפעלה.
בנוסף, אפשר להגדיר אסטרטגיה חכמה יותר לניסיון חוזר על ידי בדיקה של סוג PreloadException. אפליקציה יכולה לבחור להסיר באופן מיידי מקור שנכשל מהמנהל על סמך הודעת השגיאה או קוד סטטוס של HTTP. צריך להסיר את הפריט מזרם ממשק המשתמש כדי שבעיות הטעינה לא ישפיעו על חוויית המשתמש. אפשר גם לקבל נתונים מפורטים יותר מ-PreloadException, כמו HttpDataSourceException, כדי לבדוק את השגיאות לעומק. מידע נוסף על פתרון בעיות ב-ExoPlayer
מערכת החברים: למה צריך לשתף רכיבים עם ExoPlayer?
המחלקות DefaultPreloadManager ו-ExoPlayer מיועדות לעבוד יחד. כדי להבטיח יציבות ויעילות, הם צריכים לשתף כמה רכיבי ליבה. אם הם פועלים עם רכיבים נפרדים ולא מתואמים, זה עלול להשפיע על בטיחות השרשור ועל השימושיות של טראקים שנטענו מראש בנגן, כי אנחנו צריכים לוודא שטראקים שנטענו מראש יופעלו בנגן הנכון. בנוסף, יכול להיות שרכיבים נפרדים יתחרו על משאבים מוגבלים כמו רוחב פס ברשת וזיכרון, מה שעלול להוביל לירידה בביצועים. חלק חשוב במחזור החיים הוא טיפול בסילוק מתאים. סדר הסילוק המומלץ הוא קודם לשחרר את PreloadManager ואז את ExoPlayer.
המחלקות DefaultPreloadManager.Builder נועדו להקל על השיתוף הזה, ויש להן ממשקי API ליצירת מופע של PreloadManager ושל נגן מקושר. נבדוק למה צריך לשתף רכיבים כמו BandwidthMeter, LoadControl, TrackSelector ו-Looper. כדאי לעיין בייצוג החזותי של האינטראקציה בין הרכיבים האלה לבין ExoPlayer Playback.
מניעת קונפליקטים ברוחב הפס באמצעות BandwidthMeter משותף
הכלי BandwidthMeter מספק הערכה של רוחב הפס הזמין ברשת על סמך שיעורי העברה היסטוריים. אם PreloadManager והנגן משתמשים במופעים נפרדים, הם לא מודעים לפעילות הרשת של כל אחד מהם, מה שעלול להוביל לתרחישי כשל. לדוגמה, נניח שמשתמש צופה בסרטון, החיבור שלו לרשת נחלש, ובמקביל מתבצעת טעינה מראש של MediaSource שמתחילה הורדה אגרסיבית של סרטון עתידי. הפעילות של MediaSource בטעינה מראש תצרוך רוחב פס שדרוש לנגן הפעיל, ותגרום להשהיה של הסרטון הנוכחי. הפסקת הפעלה היא כשל משמעותי בחוויית המשתמש.
כשמשתפים BandwidthMeter יחיד, האובייקט TrackSelector יכול לבחור טראקים באיכות הכי גבוהה בהתאם לתנאי הרשת הנוכחיים ולמצב המאגר, במהלך טעינה מראש או הפעלה. לאחר מכן, המערכת יכולה לקבל החלטות חכמות כדי להגן על סשן ההפעלה הפעיל ולהבטיח חוויה חלקה.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
שמירה על עקביות באמצעות הרכיבים המשותפים LoadControl, TrackSelector ו-Renderer של ExoPlayer
- LoadControl: הרכיב הזה קובע את מדיניות האגירה, למשל כמה נתונים לאגור לפני שמתחילים בהפעלה ומתי להתחיל או להפסיק לטעון עוד נתונים. שיתוף LoadControl מבטיח שצריכת הזיכרון של הנגן ושל PreloadManager תתבסס על אסטרטגיית חציצה מתואמת אחת גם עבור מדיה שנטענה מראש וגם עבור מדיה שמופעלת באופן פעיל, וכך יימנעו התנגשויות על משאבים. כדי להבטיח עקביות, תצטרכו להקצות בצורה חכמה את שטח האחסון הזמני בהתאם למספר הפריטים שאתם טוענים מראש ולמשך הזמן של הטעינה המוקדמת. במקרים של מחלוקת, הנגן ייתן עדיפות להפעלה של הפריט הנוכחי שמוצג במסך. אם משתמשים ב-LoadControl משותף, מנהל הטעינה מראש ימשיך לטעון מראש כל עוד לא הגיע למגבלה העליונה של בייטים של מאגר היעד שהוקצו לטעינה מראש, והוא לא ימתין עד שהטעינה להפעלה תסתיים.
הערה: השיתוף של LoadControl בגרסה האחרונה של Media3 (1.8) מבטיח שה-Allocator שלו יוכל להיות משותף בצורה נכונה עם PreloadManager ועם נגן. השימוש ב-LoadControl כדי לשלוט ביעילות על הטעינה מראש הוא תכונה שתהיה זמינה בגרסה הקרובה Media3 1.9.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: הרכיב הזה אחראי לבחירת הטראקים (לדוגמה, סרטון ברזולוציה מסוימת, אודיו בשפה ספציפית) שיוטענו ויופעלו. שיתוף מבטיח שהטראקים שנבחרו במהלך הטעינה מראש הם אותם טראקים שהנגן ישתמש בהם. כך נמנע תרחיש בזבזני שבו טראק וידאו באיכות 480p נטען מראש, רק כדי שהנגן יבטל אותו מיד ויטען טראק באיכות 720p כשההפעלה תתחיל.< br /> מנהל הטעינה מראש לא צריך לשתף את אותו מופע של TrackSelector עם הנגן. במקום זאת, הם צריכים להשתמש במופע אחר של TrackSelector אבל באותו הטמעה. לכן אנחנו מגדירים את TrackSelectorFactory ולא את TrackSelector ב-DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- רכיב ה-Renderer: הרכיב הזה אחראי להבנת היכולות של הנגן בלי ליצור את רכיבי ה-Renderer המלאים. הוא בודק את התוכנית הזו כדי לראות אילו פורמטים של סרטונים, אודיו וטקסט הנגן הסופי יתמוך בהם. כך המערכת יכולה לבחור ולהוריד רק את טראק המדיה התואם, ולא לבזבז רוחב פס על תוכן שהנגן לא יכול להפעיל.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
מידע נוסף על רכיבי ExoPlayer
כלל הזהב: כלי נפוץ להפעלת לולאה שמתאים לכולם
אפשר לציין באופן מפורש את ה-thread שבו אפשר לגשת למופע של ExoPlayer על ידי העברת Looper כשיוצרים את הנגן. אפשר לשלוח שאילתה לגבי ה-Looper של ה-thread שממנו צריך לגשת אל Player באמצעות Player.getApplicationLooper. השימוש ב-Looper משותף בין הנגן לבין PreloadManager מבטיח שכל הפעולות באובייקטים המשותפים של המדיה יסודרו בסדר עוקב בתור ההודעות של השרשור היחיד. כך אפשר לצמצם את באגים של פעולות מקבילות.
כל האינטראקציות בין PreloadManager לבין הנגן עם מקורות המדיה שצריך לטעון או לטעון מראש צריכות להתרחש באותו שרשור הפעלה. שיתוף ה-Looper הוא חובה כדי להבטיח את בטיחות השרשור, ולכן צריך לשתף את PlaybackLooper בין PreloadManager לבין הנגן.
ה-PreloadManager מכין ברקע אובייקט MediaSource עם שמירת מצב. כשקוד ממשק המשתמש קורא ל-player.setMediaSource(mediaSource), מתבצעת העברה של האובייקט המורכב הזה עם המצב מ-MediaSource של טרום הטעינה אל הנגן. במקרה כזה, כל PreloadMediaSource מועבר מהמנהל לנגן. כל האינטראקציות וההעברות האלה צריכות להתרחש באותו PlaybackLooper.
אם PreloadManager ו-ExoPlayer פעלו על שרשורים שונים, יכול להיות שיתרחש מרוץ תהליכים. יכול להיות שהשרשור של PreloadManager משנה את המצב הפנימי של MediaSource (למשל, כותב נתונים חדשים למאגר) בדיוק ברגע שהשרשור של הנגן מנסה לקרוא ממנו. התוצאה היא התנהגות בלתי צפויה, IllegalStateException שקשה לנפות באגים.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
בואו נראה איך אפשר לשתף את כל הרכיבים שלמעלה בין ExoPlayer לבין DefaultPreloadManager בהגדרה עצמה.
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
.setBandwidthMeter(customBandwidthMeter)
.setLoadControl(customLoadControl)
.setMediaSourceFactory(customMediaSourceFactory)
.setTrackSelectorFactory(customTrackSelectorFactory)
.setRenderersFactory(customRenderersFactory)
.setPreloadLooper(playbackLooper)
val preloadManager = val preloadManagerBuilder.build()
טיפ: אם אתם משתמשים ברכיבי ברירת המחדל ב-ExoPlayer כמו DefaultLoadControl וכו', אתם לא צריכים לשתף אותם באופן מפורש עם DefaultPreloadManager. כשיוצרים את מופע ExoPlayer באמצעות buildExoPlayer של DefaultPreloadManager.Builder, הרכיבים האלה מקושרים אוטומטית זה לזה, אם משתמשים בהטמעות ברירת המחדל עם הגדרות ברירת המחדל. אבל אם אתם משתמשים ברכיבים מותאמים אישית או בהגדרות מותאמות אישית, אתם צריכים להודיע במפורש ל-DefaultPreloadManager על כך באמצעות ממשקי ה-API שלמעלה.
טעינה מראש שמוכנה לייצור: התבנית של חלון הזזה
בפיד דינמי, משתמש יכול לגלול בכמות כמעט אינסופית של תוכן. אם מוסיפים סרטונים ל-DefaultPreloadManager באופן רציף בלי אסטרטגיית הסרה מתאימה, בסופו של דבר תתרחש שגיאת OutOfMemoryError. כל MediaSource שנטען מראש מחזיק SampleQueue, שמקצה מאגרי זיכרון. כשהם מצטברים, הם יכולים לנצל את כל נפח ה-heap של האפליקציה. הפתרון הוא אלגוריתם שאתם אולי כבר מכירים, שנקרא חלון נע. התבנית של חלון הזזה שומרת בזיכרון קבוצה קטנה וניתנת לניהול של פריטים שסמוכים מבחינה לוגית למיקום הנוכחי של המשתמש בפיד. בזמן שהמשתמש גולל, ה "חלון" הזה של פריטים מנוהלים זז איתו, ומוסיף פריטים חדשים שנכנסים לתצוגה, וגם מסיר פריטים שכבר לא בתצוגה.
הטמעה של תבנית חלון הזזה
חשוב להבין ש-PreloadManager לא מספק שיטה מובנית של setWindowSize(). החלון הנע הוא תבנית עיצוב שאתם, המפתחים, אחראים להטמיע באמצעות השיטות הפרימיטיביות add() ו-remove(). הלוגיקה של האפליקציה צריכה לקשר בין אירועים בממשק המשתמש, כמו גלילה או שינוי דף, לבין קריאות ה-API האלה. אם אתם רוצים לראות דוגמה לקוד, אפשר לעיין בדוגמה socialite שכוללת גם PreloadManagerWrapper שמדמה חלון נע.
אל תשכחו להוסיף את preloadManager.remove(mediaItem) בהטמעה שלכם כשהפריט כבר לא צפוי להופיע בקרוב בצפייה של המשתמש. הסיבה העיקרית לבעיות בזיכרון בהטמעות של טעינה מראש היא אי-הסרה של פריטים שכבר לא קרובים למשתמש. הקריאה ל-remove() מוודאת שמשחררים משאבים שעוזרים לשמור על שימוש מוגבל ויציב בזיכרון של האפליקציה.
שיפור אסטרטגיית טעינה מראש לפי קטגוריות באמצעות TargetPreloadStatusControl
אחרי שהגדרנו מה לטעון מראש (הפריטים בחלון), אפשר להחיל אסטרטגיה מוגדרת היטב לגבי כמות הטעינה מראש של כל פריט. כבר ראינו איך אפשר להשיג את רמת הפירוט הזו באמצעות ההגדרה TargetPreloadStatusControl בחלק 1.
כדי להזכיר, לפריט במיקום +/- 1 יכול להיות סיכוי גבוה יותר להיות מושמע מאשר לפריט במיקום +/- 4. אפשר להקצות יותר משאבים (רשת, מעבד (CPU), זיכרון) לפריטים שהמשתמש צפוי לצפות בהם בהמשך. כך נוצרת אסטרטגיית 'טעינה מראש' שמבוססת על קרבה, והיא המפתח לאיזון בין הפעלה מיידית לבין שימוש יעיל במשאבים.
אפשר להשתמש בנתוני ניתוח באמצעות PreloadManagerListener, כפי שמוסבר בקטעים הקודמים, כדי להחליט על אסטרטגיית משך הטעינה מראש.
סיכום והשלבים הבאים
עכשיו יש לכם את הידע המתקדם שדרוש כדי ליצור פידים של מדיה במהירות, ביציבות וביעילות מבחינת שימוש במשאבים באמצעות DefaultPreloadManager של Media3.
בואו נסכם את המסקנות העיקריות:
- משתמשים ב-PreloadManagerListener כדי לאסוף תובנות מניתוח הנתונים וליישם טיפול יעיל בשגיאות.
- כדי לוודא שרכיבים חשובים משותפים, תמיד צריך להשתמש ב-DefaultPreloadManager.Builder יחיד כדי ליצור את מופעי הניהול והנגן.
- כדי למנוע את השגיאה OutOfMemoryError, צריך להטמיע את התבנית של חלון הזזה על ידי ניהול פעיל של הקריאות add() ו-remove().
- אפשר להשתמש ב-TargetPreloadStatusControl כדי ליצור אסטרטגיית טעינה מראש חכמה ומדורגת, שמאזנת בין ביצועים לבין צריכת משאבים.
מה השלב הבא בחלק 3: שמירת מדיה בזיכרון המטמון עם טעינה מראש
טעינה מראש של נתונים לזיכרון מספקת שיפור מיידי בביצועים, אבל יכולות להיות לכך השלכות. אחרי שהאפליקציה נסגרת או שהמדיה שנטענה מראש מוסרת מהמנהל, הנתונים נעלמים. כדי להשיג רמת אופטימיזציה עקבית יותר, אפשר לשלב טעינה מראש עם שמירת נתונים במטמון בדיסק. התכונה הזו נמצאת בשלבי פיתוח מתקדמים ותהיה זמינה בקרוב, תוך כמה חודשים.
רוצה לשתף משוב? נשמח לשמוע ממך.
כדאי לעקוב אחרי העדכונים שלנו, ולנסות להגביר את מהירות ההפעלה של הסרטונים! 🚀
להמשך הקריאה
-
חדשות על מוצרים
באפליקציות של היום שמתמקדות במדיה, חשוב לספק חוויית הפעלה חלקה ללא הפרעות כדי שהמשתמשים ייהנו. המשתמשים מצפים שהסרטונים יתחילו לפעול באופן מיידי ויופעלו בצורה חלקה ללא הפסקות.
Mayuri Khinvasara Khabya • משך הקריאה: 8 דקות
-
חדשות על מוצרים
אם אתם מפתחי Android שרוצים להטמיע תכונות חדשניות מבוססות-AI באפליקציה שלכם, לאחרונה השקנו עדכונים חדשים ומתקדמים.
Thomas Ezan • משך הקריאה: 3 דקות
-
חדשות על מוצרים
Android 17 הגיע לגרסת בטא 4, גרסת הבטא המתוזמנת האחרונה של מחזור הפרסום הזה, אבן דרך קריטית לתאימות אפליקציות ויציבות הפלטפורמה.
Daniel Galpin • משך הקריאה: 4 דקות
כדאי תמיד להיות בעניינים
רוצים לקבל טיפים עדכניים לפיתוח Android ישירות לאימייל כל שבוע?