MediaPlayer Singleton - Quản lý phát nhạc tập trung
MediaPlayer là tài nguyên nặng (heavy resource). Singleton pattern giúp tái sử dụng một instance, tránh tạo nhiều MediaPlayer gây rò rỉ bộ nhớ hoặc lỗi "MediaPlayer went away with unhandled events".
Bước 1: Tạo dự án
Android Studio → File → New Project. Thêm file âm thanh sample.mp3 vào res/raw/.
Bước 2: Giao diện (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/play"
android:text="Phát nhạc từ Singleton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Bước 3: MainActivity.java
package com.example.mediaplayersingleton;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
Button btnPlay;
MediaPlayerSingleton mediaPlayerSingleton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnPlay = findViewById(R.id.play);
mediaPlayerSingleton = MediaPlayerSingleton.getInstance();
mediaPlayerSingleton.init(getApplicationContext());
btnPlay.setOnClickListener(v -> {
MediaPlayer player = mediaPlayerSingleton.getMediaPlayer();
if (player != null) {
player.start();
} else {
Toast.makeText(this, "MediaPlayer chưa sẵn sàng", Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mediaPlayerSingleton.release();
}
}
Bước 4: MediaPlayerSingleton.java
package com.example.mediaplayersingleton;
import android.content.Context;
import android.media.MediaPlayer;
public class MediaPlayerSingleton {
private static MediaPlayerSingleton instance;
private MediaPlayer mediaPlayer;
private Context appContext;
private MediaPlayerSingleton() { }
public static synchronized MediaPlayerSingleton getInstance() {
if (instance == null) {
instance = new MediaPlayerSingleton();
}
return instance;
}
public void init(Context context) {
if (appContext == null) {
this.appContext = context.getApplicationContext();
}
}
public MediaPlayer getMediaPlayer() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(appContext, R.raw.sample);
mediaPlayer.setOnCompletionListener(mp -> {
mp.seekTo(0); // Tự động quay về đầu khi hết bài
});
}
return mediaPlayer;
}
public void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
public void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.reset();
mediaPlayer = null; // Cho phép tạo lại lần sau
}
}
public void release() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
public boolean isPlaying() {
return mediaPlayer != null && mediaPlayer.isPlaying();
}
}
Chạy ứng dụng
Nhấn nút → phát file sample.mp3 từ raw resource. Nhấn lại khi đang phát → tiếp tục từ vị trí hiện tại (do không reset).
Các phương thức quan trọng
getMediaPlayer(): Lazy initialization - chỉ tạo khi cần.release(): **Bắt buộc gọi trong onDestroy()** để giải phóng native resources.stop()+reset(): Dừng và chuẩn bị cho lần phát sau.setOnCompletionListener: Xử lý khi bài hát kết thúc.
Cải tiến cho Production
// Thêm MediaPlayer.OnErrorListener
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Log.e("MediaPlayer", "Error: " + what);
release();
return true; // Đã xử lý lỗi
});
// Thêm OnPreparedListener cho streaming
mediaPlayer.setOnPreparedListener(mp -> mp.start());
// Xử lý Audio Focus
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(focusChange -> {}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
⚠️ Lưu ý quan trọng
- Luôn gọi
release()khi không dùng nữa (onDestroy, onStop). - Không giữ MediaPlayer static mà không release - gây leak native memory.
- Xử lý lifecycle: pause trong onPause, resume trong onResume.
- Dùng
MediaPlayer.create()thay vì new MediaPlayer() + setDataSource() + prepare() cho file raw/local.