Phát nội dung nghe nhìn trong nền

Bạn có thể phát nội dung nghe nhìn trong nền ngay cả khi ứng dụng của bạn không ở 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 sẽ nhúng MediaPlayer vào một dịch vụ MediaBrowserServiceCompat và để dịch vụ này 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 một trình phát 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 được những mong đợi đó, 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 nó trong một dịch vụ.

Chạy không đồng bộ

Giống như Activity, theo mặc định, tất cả công việc trong Service đều được thực hiện trong một luồng duy nhất. 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ẽ 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 các lệnh gọi chặn 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:

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ộ

Đối với 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 các tài nguyên không đồng bộ, bạn nên thông báo cho ứng dụng của mình về các 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 trong phiên bản 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 Error (Lỗi). Bạn phải đặt lại trạng thái này trước khi có thể sử dụng lại. Để biết thông tin chi tiết, hãy xem sơ đồ trạng thái đầy đủ cho 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 của bạn, ví dụ: bằng cách đặt thiết bị ở chế độ 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 duy trì trạng thái sẵn sàng 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 tạo 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 hoạt động. 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, thì có thể bạn cũng muốn giữ một WifiLock mà bạn phải tự lấy và giải phó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 giải phóng khoá:

Kotlin

wifiLock.release()

Java

wifiLock.release();

Dọn dẹp

Như đã đề cập trước đó, một đố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 bạn không cần dùng đến nữa. Bạn nên 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 một khoảng thời gian trước khi trình thu thập rác thu hồi MediaPlayer, vì trình thu thập rác chỉ nhạy cảm với nhu cầu về bộ nhớ chứ không nhạy cảm với tình trạng thiếu các tài nguyên khác liên quan đến nội dung nghe nhìn. 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();
   }
}

Bạn cũng nên tìm những cơ hội khác để giải phóng MediaPlayer, ngoài việc giải phóng khi tắt máy. Ví dụ: nếu dự kiến không thể phát nội dung nghe nhìn trong một khoảng thời gian dài (ví dụ: sau khi mất quyền phát âm thanh), bạn chắc chắn nên giải phóng MediaPlayer hiện có và tạo lại sau. Mặt khác, nếu chỉ muốn dừng phát trong một thời gian rất ngắn, bạn nên giữ lại MediaPlayer để tránh hao tổn chi phí 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 nghe nhìn trong ứng dụng của bạn. Đọc thêm về giải pháp này.

Những trang này đề cập đến các chủ đề liên quan đến việc ghi, lưu trữ và phát âm thanh cũng như video: