Вы можете воспроизводить мультимедиа в фоновом режиме, даже когда ваше приложение не отображается на экране, например, пока пользователь взаимодействует с другими приложениями.
Для этого необходимо встроить MediaPlayer в службу MediaBrowserServiceCompat и настроить его взаимодействие с MediaBrowserCompat в другом Activity.
Будьте осторожны при реализации такой конфигурации клиент-сервер. Существуют ожидания относительно того, как проигрыватель, работающий в фоновом режиме, взаимодействует с остальной частью системы. Если ваше приложение не соответствует этим ожиданиям, пользователь может получить негативный опыт. Подробнее см. раздел «Создание аудиоприложения» .
На этой странице описаны специальные инструкции по управлению медиаплеером при его использовании в рамках сервиса.
Выполнять асинхронно
Подобно Activity , вся работа в Service по умолчанию выполняется в одном потоке. Фактически, когда вы запускаете Activity и Service из одного и того же приложения, они по умолчанию используют один и тот же поток («основной поток»).
Сервисы должны быстро обрабатывать входящие намерения и никогда не выполнять длительные вычисления при ответе на них. Любую ресурсоемкую работу или блокирующие вызовы следует выполнять асинхронно: либо из отдельного потока, который вы реализуете самостоятельно, либо используя многочисленные средства асинхронной обработки фреймворка.
Например, при использовании MediaPlayer из основного потока следует:
- Вместо функции prepare
prepareAsync()prepare(), и - Реализуйте обработчик
MediaPlayer.OnPreparedListener, чтобы получать уведомления о завершении подготовки и начале воспроизведения.
Например:
Котлин
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 :
Котлин
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 переходит в состояние Error . Перед повторным использованием его необходимо сбросить. Подробности см. в полной диаграмме состояний класса MediaPlayer .
Используйте блокировку пробуждения
При воспроизведении или потоковой передаче музыки в фоновом режиме необходимо использовать блокировку пробуждения, чтобы предотвратить вмешательство системы в воспроизведение, например, путем перевода устройства в спящий режим.
Блокировка пробуждения — это сигнал системе о том, что ваше приложение использует функции, которые должны оставаться доступными даже в режиме ожидания телефона.
Чтобы гарантировать, что процессор продолжит работу во время воспроизведения вашего MediaPlayer , вызовите метод setWakeMode() при инициализации MediaPlayer . MediaPlayer будет удерживать указанную блокировку во время воспроизведения и снимать её при паузе или остановке.
Котлин
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);
Однако блокировка пробуждения, полученная в этом примере, гарантирует лишь то, что процессор останется в бодрствующем состоянии. Если вы транслируете мультимедиа по сети и используете Wi-Fi, вам, вероятно, также потребуется блокировка WifiLock , которую необходимо получить и снять вручную. Поэтому, когда вы начинаете подготовку MediaPlayer с удаленным URL-адресом, вам следует создать и получить блокировку WifiLock.
Например:
Котлин
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();
Когда вы ставите воспроизведение медиафайлов на паузу или останавливаете его, или когда вам больше не нужна сеть, следует снять блокировку:
Котлин
wifiLock.release()
Java
wifiLock.release();
Выполните очистку
Как уже упоминалось, объект MediaPlayer может потреблять значительное количество системных ресурсов, поэтому его следует хранить только до тех пор, пока это необходимо, и вызывать release() , когда работа с ним завершена. Важно вызывать этот метод очистки явно, а не полагаться на системную сборку мусора, поскольку сборщику мусора может потребоваться некоторое время, прежде чем он освободит MediaPlayer , так как он чувствителен только к потребностям в памяти, а не к нехватке других ресурсов, связанных с мультимедиа. Поэтому, если вы используете службу, всегда следует переопределять метод onDestroy() , чтобы убедиться, что вы освобождаете MediaPlayer :
Котлин
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 — рекомендуемое решение для воспроизведения мультимедиа в вашем приложении. Узнайте больше об этом.
На этих страницах рассматриваются вопросы, касающиеся записи, хранения и воспроизведения аудио- и видеоматериалов: