הפעלת מדיה ברקע

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

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

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

בדף הזה מוסבר איך לנהל את MediaPlayer כשמטמיעים אותו בתוך שירות.

הרצה אסינכרונית

בדומה לActivity, כל העבודה בService מתבצעת כברירת מחדל בשרשור אחד. למעשה, כשמריצים פעילות ושירות מאותה אפליקציה, הם משתמשים באותו השרשור (השרשור הראשי) כברירת מחדל.

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

לדוגמה, כשמשתמשים ב-MediaPlayer מהשרשור הראשי, צריך:

לדוגמה:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

טיפול בשגיאות אסינכרוניות

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

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

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

שימוש בחסימות מצב שינה

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

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

כדי לוודא שה-CPU ימשיך לפעול בזמן שקובץ MediaPlayer מתנגן, צריך להפעיל את ה-method‏ setWakeMode() כשמפעילים את MediaPlayer. התג MediaPlayer מחזיק את הנעילה שצוינה בזמן ההפעלה ומשחרר את הנעילה כשההפעלה מושהית או מופסקת:

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

עם זאת, חסימת מצב השינה שמתקבלת בדוגמה הזו מבטיחה רק שהמעבד (CPU) יישאר פעיל. אם אתם מזרמים מדיה ברשת ואתם משתמשים ב-Wi-Fi, כדאי להחזיק גם ב-WifiLock, שצריך לקבל ולשחרר באופן ידני. לכן, כשמתחילים להכין את MediaPlayer עם כתובת ה-URL המרוחקת, צריך ליצור את נעילת ה-Wi-Fi ולקבל אותה.

לדוגמה:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

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

Kotlin

wifiLock.release()

Java

wifiLock.release();

ביצוע ניקוי

כמו שצוין קודם, אובייקט MediaPlayer יכול לצרוך כמות משמעותית של משאבי מערכת, ולכן כדאי לשמור אותו רק למשך הזמן שצריך, ולהפעיל את release() כשמסיימים להשתמש בו. חשוב להפעיל את שיטת הניקוי הזו באופן מפורש, ולא להסתמך על garbage collection של המערכת, כי יכול להיות שיעבור זמן עד שמנגנון garbage collection יפנה את MediaPlayer. המנגנון הזה רגיש רק לצרכים של הזיכרון, ולא למחסור במשאבים אחרים שקשורים למדיה. לכן, אם אתם משתמשים בשירות, אתם צריכים תמיד לבטל את השיטה onDestroy() כדי לוודא שאתם משחררים את MediaPlayer:

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

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

מידע נוסף

‫Jetpack Media3 הוא הפתרון המומלץ להפעלת מדיה באפליקציה. מידע נוסף

בדפים האלה מפורטים נושאים שקשורים להקלטה, לאחסון ולהפעלה של אודיו וסרטונים: