애플리케이션이 화면에 표시되지 않는 경우에도 백그라운드에서 미디어를 재생할 수 있습니다(예: 사용자가 다른 애플리케이션과 상호작용하는 동안).
이렇게 하려면 MediaPlayer를 MediaBrowserServiceCompat
서비스에 삽입하고 다른 활동에서 MediaBrowserCompat
와 상호작용하게 합니다.
이 클라이언트 및 서버 설정을 구현할 때는 주의해야 합니다. 백그라운드 서비스에서 실행되는 플레이어가 나머지 시스템과 상호작용하는 데는 예상되는 방식이 있습니다. 애플리케이션이 이러한 예상을 충족하지 못하면 사용자 환경이 좋지 않을 수 있습니다. 자세한 내용은 오디오 앱 빌드를 참고하세요.
이 페이지에서는 서비스 내부에서 MediaPlayer를 구현할 때 MediaPlayer를 관리하는 특수 안내를 설명합니다.
비동기 실행
Activity
와 마찬가지로 Service
의 모든 작업은 기본적으로 단일 스레드에서 실행됩니다. 실제로 동일한 애플리케이션에서 활동과 서비스를 실행하면 기본적으로 동일한 스레드('기본 스레드')를 사용합니다.
서비스는 들어오는 인텐트를 빠르게 처리하고 인텐트에 응답할 때 너무 긴 계산을 실행해서는 안 됩니다. 과도한 작업이나 호출 차단은 직접 구현한 다른 스레드에서 또는 프레임워크의 여러 비동기 처리 기능을 사용하여 비동기로 실행해야 합니다.
예를 들어 기본 스레드에서 MediaPlayer
를 사용하는 경우 다음을 실행해야 합니다.
prepare()
대신prepareAsync()
를 호출합니다.- 준비가 완료되고 재생을 시작할 수 있을 때 알림을 받으려면
MediaPlayer.OnPreparedListener
를 구현합니다.
예를 들면 다음과 같습니다.
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()
}
}
자바
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!
}
}
자바
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
클래스의 전체 상태 다이어그램을 참고하세요.
wake lock 사용
백그라운드에서 음악을 재생하거나 스트리밍할 때는 기기를 절전 모드로 전환하는 등 시스템이 재생을 방해하지 못하도록 하기 위해 wake lock을 사용해야 합니다.
wake lock은 애플리케이션이 휴대전화가 유휴 상태인 경우에도 계속 사용 가능해야 하는 기능을 이용하고 있다고 시스템에 알리는 신호입니다.
MediaPlayer
가 재생되는 동안 CPU가 계속 실행되도록 하려면 MediaPlayer
를 초기화할 때 setWakeMode()
메서드를 호출합니다. MediaPlayer
는 재생하는 동안 지정된 잠금을 유지하고 일시중지 또는 중지될 때 잠금을 해제합니다.
Kotlin
mediaPlayer = MediaPlayer().apply {
// ... other initialization here ...
setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}
자바
mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
그러나 이 예에서 획득한 wake lock은 CPU의 절전 모드가 해제된 상태만을 보장합니다. 네트워크를 통해 미디어를 스트리밍하고 Wi-Fi를 사용하고 있다면 WifiLock
도 유지하는 것이 좋을 수 있습니다. WifiLock은 수동으로 가져오고 해제해야 합니다. 따라서 원격 URL로 MediaPlayer
준비를 시작하면 Wi-Fi 잠금을 만들고 가져와야 합니다.
예를 들면 다음과 같습니다.
Kotlin
val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")
wifiLock.acquire()
자바
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
미디어를 일시중지 또는 중지하거나 네트워크가 더 이상 필요하지 않으면 잠금을 해제해야 합니다.
Kotlin
wifiLock.release()
자바
wifiLock.release();
정리 실행
앞에서 언급했듯이 MediaPlayer
객체는 상당량의 시스템 리소스를 소비할 수 있으므로 필요할 때만 유지하고 작업이 끝나면 release()
를 호출해야 합니다. 시스템 가비지 컬렉션을 사용하기보다는 이 정리 메서드를 명시적으로 호출하는 것이 중요합니다. 가비지 컬렉터는 메모리 요구에만 민감하고 다른 미디어 관련 리소스의 부족에는 민감하지 않아서 MediaPlayer
를 회수하는 데 시간이 걸리기 때문입니다. 따라서 서비스를 사용하는 경우에는 항상 onDestroy()
메서드를 재정의하여 MediaPlayer
를 해제해야 합니다.
Kotlin
class MyService : Service() {
private var mediaPlayer: MediaPlayer? = null
// ...
override fun onDestroy() {
super.onDestroy()
mediaPlayer?.release()
}
}
자바
public class MyService extends Service {
MediaPlayer mediaPlayer;
// ...
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) mediaPlayer.release();
}
}
종료할 때 해제하는 것 외에 MediaPlayer
를 해제하는 다른 기회를 항상 찾아야 합니다. 예를 들어 오랫동안 미디어를 재생할 수 없을 것으로 예상한다면 (예: 오디오 포커스를 잃은 경우) 기존 MediaPlayer
를 확실히 해제하고 나중에 다시 만들어야 합니다. 반면 아주 짧은 시간만 재생을 중지할 것으로 예상한다면 다시 만들고 준비하는 오버헤드를 방지하기 위해 MediaPlayer
를 유지해야 할 수 있습니다.
자세히 알아보기
Jetpack Media3는 앱에서 미디어를 재생하는 데 권장되는 솔루션입니다. 자세한 내용은 자세히 알아보세요.
이 페이지에서는 오디오와 동영상 녹음/녹화, 저장 및 재생과 관련된 주제를 다룹니다.