Bạn có thể phát nội dung nghe nhìn ở chế độ nền ngay cả khi ứng dụng không hiển thị trên màn hình, chẳng hạn như khi người dùng đang tương tác với các ứng dụng khác.
Để làm như vậy, bạn nhúng MediaPlayer vào dịch vụ MediaBrowserServiceCompat
và cho phép tương tác với MediaBrowserCompat
trong một hoạt động khác.
Hãy thận trọng khi triển khai chế độ thiết lập máy khách và máy chủ này. Có những kỳ vọng về cách người chơi chạy trong dịch vụ nền tương tác với phần còn lại của hệ thống. Nếu ứng dụng của bạn không đáp ứng những kỳ vọng đó, người dùng có thể có trải nghiệm không tốt. Hãy xem bài viết Tạo ứng dụng âm thanh để biết thông tin chi tiết.
Trang này mô tả hướng dẫn đặc biệt để quản lý MediaPlayer khi bạn triển khai MediaPlayer bên trong một dịch vụ.
Chạy không đồng bộ
Giống như Activity
, tất cả công việc trong Service
đều được thực hiện trong một luồng theo mặc định. Trên thực tế, khi bạn chạy một hoạt động và một dịch vụ từ cùng một ứng dụng, theo mặc định, chúng sẽ sử dụng cùng một luồng ("luồng chính").
Các dịch vụ phải xử lý nhanh các ý định đến và không bao giờ thực hiện các phép tính dài khi phản hồi các ý định đó. Bạn phải thực hiện mọi công việc nặng hoặc lệnh gọi chặn một cách không đồng bộ: từ một luồng khác mà bạn tự triển khai hoặc sử dụng nhiều cơ sở của khung để xử lý không đồng bộ.
Ví dụ: khi sử dụng MediaPlayer
từ luồng chính, bạn nên:
- Gọi
prepareAsync()
thay vìprepare()
và - Triển khai
MediaPlayer.OnPreparedListener
để được thông báo khi quá trình chuẩn bị hoàn tất và bạn có thể bắt đầu chơi.
Ví dụ:
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();
}
}
Xử lý lỗi không đồng bộ
Trên các thao tác đồng bộ, lỗi được báo hiệu bằng một ngoại lệ hoặc mã lỗi. Tuy nhiên, khi sử dụng tài nguyên không đồng bộ, bạn nên thông báo cho ứng dụng về lỗi một cách thích hợp. Trong trường hợp MediaPlayer
, bạn triển khai MediaPlayer.OnErrorListener
và đặt thuộc tính này trong thực thể 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!
}
}
Khi xảy ra lỗi, MediaPlayer
sẽ chuyển sang trạng thái Lỗi. Bạn phải đặt lại trước khi có thể sử dụng lại. Để biết thông tin chi tiết, hãy xem biểu đồ trạng thái đầy đủ của lớp MediaPlayer
.
Sử dụng khoá chế độ thức
Khi phát hoặc phát trực tuyến nhạc ở chế độ nền, bạn phải sử dụng khoá chế độ thức để ngăn hệ thống can thiệp vào quá trình phát, chẳng hạn như bằng cách đưa thiết bị vào trạng thái ngủ.
Khoá chế độ thức là một tín hiệu cho hệ thống biết rằng ứng dụng của bạn đang sử dụng các tính năng cần được duy trì ngay cả khi điện thoại ở trạng thái rảnh.
Để đảm bảo CPU tiếp tục chạy trong khi MediaPlayer
đang phát, hãy gọi phương thức setWakeMode()
khi bạn khởi chạy MediaPlayer
. MediaPlayer
giữ khoá đã chỉ định trong khi phát và mở khoá khi tạm dừng hoặc dừng:
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);
Tuy nhiên, khoá chế độ thức thu được trong ví dụ này chỉ đảm bảo rằng CPU vẫn ở trạng thái thức. Nếu đang phát trực tuyến nội dung nghe nhìn qua mạng và đang sử dụng Wi-Fi, bạn cũng nên giữ một WifiLock
. Bạn phải lấy và nhả WifiLock
theo cách thủ công. Vì vậy, khi bắt đầu chuẩn bị MediaPlayer
bằng URL từ xa, bạn nên tạo và lấy khoá Wi-Fi.
Ví dụ:
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();
Khi tạm dừng hoặc dừng phát nội dung nghe nhìn hoặc khi không cần mạng nữa, bạn nên nhả khoá:
Kotlin
wifiLock.release()
Java
wifiLock.release();
Thực hiện dọn dẹp
Như đã đề cập trước đó, đối tượng MediaPlayer
có thể tiêu tốn một lượng đáng kể tài nguyên hệ thống, vì vậy, bạn chỉ nên giữ đối tượng này trong thời gian cần thiết và gọi release()
khi đã hoàn tất. Điều quan trọng là bạn phải gọi phương thức dọn dẹp này một cách rõ ràng thay vì dựa vào tính năng thu thập rác của hệ thống vì có thể mất chút thời gian trước khi trình thu gom rác thu hồi MediaPlayer
, vì trình thu gom rác chỉ nhạy cảm với nhu cầu bộ nhớ chứ không phải tình trạng thiếu tài nguyên liên quan đến nội dung nghe nhìn khác. Vì vậy, trong trường hợp đang sử dụng một dịch vụ, bạn phải luôn ghi đè phương thức onDestroy()
để đảm bảo bạn đang phát hành 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();
}
}
Ngoài việc phát hành MediaPlayer
khi bị tắt, bạn cũng nên luôn tìm kiếm các cơ hội khác để phát hành. Ví dụ: nếu dự kiến không thể phát nội dung đa phương tiện trong một khoảng thời gian dài (ví dụ: sau khi mất tiêu điểm âm thanh), bạn chắc chắn phải phát hành MediaPlayer
hiện có và tạo lại sau. Mặt khác, nếu chỉ dự kiến dừng phát trong một khoảng thời gian rất ngắn, bạn nên giữ lại MediaPlayer
để tránh hao tổn khi tạo và chuẩn bị lại.
Tìm hiểu thêm
Jetpack Media3 là giải pháp được đề xuất để phát nội dung đa phương tiện trong ứng dụng của bạn. Hãy đọc thêm về giải pháp này.
Các trang này đề cập đến các chủ đề liên quan đến việc ghi âm, lưu trữ và phát âm thanh và video: