一、流程
整体架构概览

客户端 → 服务端:控制指令流向
- 发起控制请求
// 客户端:通过 TransportControls 发送指令
mediaController.getTransportControls().play();
mediaController.getTransportControls().pause();
mediaController.getTransportControls().seekTo(1000);
-
IPC 传输层
// MediaControllerCompat 内部实现(简化)
public void play() {
// 通过 IMediaSession 接口跨进程调用
mSessionBinder.play(mPackageName, mCb, getSeqNumber());
}
实际是通过 Binder 调用服务端的 IMediaSession.Stub 实现。
-
服务端接收处理
// MediaSessionService 端
private final IMediaSession.Stub mSessionBinder = new IMediaSession.Stub() {
@Override
public void play(String packageName, IMediaControllerCallback cb, int seq) {
// 切换到主线程处理
mHandler.post(() -> {
// 调用 MediaSession.Callback
mSessionCallback.onPlay();
});
}
}; -
最终执行播放逻辑
// 你的 MediaSession.Callback 实现
@Override
public void onPlay() {
Log.d(TAG, "收到播放指令");
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
mediaPlayer.start();
// 关键:更新 PlaybackState 通知所有监听者
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);
}
}
服务端 → 客户端:状态广播流向
-
服务端状态变更
private void updatePlaybackState(int state) {
// 构建新状态
PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
.setState(state, position, playbackRate)
.setActions(availableActions)
.build();// 关键:设置到 MediaSession mediaSession.setPlaybackState(playbackState);}
-
系统广播机制
MediaSession 内部维护所有连接的 MediaController 列表:
// MediaSession 内部
void setPlaybackState(PlaybackStateCompat state) {
mPlaybackState = state;
// 遍历所有控制器发送更新
for (MediaController controller : connectedControllers) {
controller.notifyPlaybackStateChanged(state);
}
}
-
客户端接收回调
// 客户端的 Callback
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
runOnUiThread(() -> {
// 更新 UI
updatePlayButton(state.getState() == STATE_PLAYING);
updateProgress(state.getPosition());
});
}
关键源码路径(Android 11)
frameworks/base/media/java/android/media/session/
├── MediaSession.java # 服务端实现
├── MediaController.java # 客户端实现
└── IMediaSession.aidl # Binder 接口定义
frameworks/support/v4/media/session/
├── MediaSessionCompat.java # 兼容库封装
└── MediaControllerCompat.java
调试与问题排查
- 常见问题原因
现象 排查点
控制无响应 MediaSession 未 setActive(true)
状态不同步 忘记调用 setPlaybackState()
回调丢失 未 registerCallback() 或线程阻塞
- 调试代码
// 打印调用堆栈
Log.d("MediaFlow", "调用来源: " + Thread.currentThread().getStackTrace()[2]);
// 验证 Session 活性
if (!mediaSession.isActive()) {
Log.w(TAG, "Session 未激活,控制指令将被忽略");
}
// 检查控制器连接
if (mediaController.getSessionToken() == null) {
Log.e(TAG, "Controller 未连接到 Session");
}
二、例子
包含音频焦点和方控处理
服务端 (MusicPlayerService.java)
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import java.io.IOException;
public class MusicPlayerService extends Service
implements AudioManager.OnAudioFocusChangeListener {
private static final String TAG = "MusicPlayerService";
// 音频焦点相关
private AudioManager audioManager;
private AudioFocusRequest audioFocusRequest;
// MediaSession 组件
private MediaSessionCompat mediaSession;
private MediaPlayer mediaPlayer;
private final IBinder binder = new PlayerBinder();
// 播放状态
private boolean isPrepared = false;
private boolean hasAudioFocus = false;
private int currentState = PlaybackStateCompat.STATE_NONE;
private boolean resumeAfterFocusLoss = false; // 焦点恢复后是否自动续播
public class PlayerBinder extends Binder {
public AudioFocusPlayerService getService() {
return AudioFocusPlayerService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "服务创建");
initAudioManager();
initMediaSession();
initMediaPlayer();
}
/**
* 初始化音频管理器
*/
private void initAudioManager() {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// 创建音频焦点请求(Android O+)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(attributes)
.setAcceptsDelayedFocusGain(false)
.setWillPauseWhenDucked(true)
.setOnAudioFocusChangeListener(this)
.build();
}
}
/**
* 初始化 MediaSession
*/
private void initMediaSession() {
mediaSession = new MediaSessionCompat(this, "AudioFocusPlayerSession");
mediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
);
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
KeyEvent keyEvent = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
return handleKeyEvent(keyEvent);
}
return super.onMediaButtonEvent(mediaButtonIntent);
}
@Override
public void onPlay() {
if (requestAudioFocus()) {
playInternal();
}
}
@Override
public void onPause() {
pauseInternal();
}
@Override
public void onStop() {
stopInternal();
abandonAudioFocus();
}
@Override
public void onSeekTo(long pos) {
seekTo(pos);
}
});
// 必须激活会话才能接收方控事件
mediaSession.setActive(true);
updatePlaybackState(PlaybackStateCompat.STATE_NONE);
}
/**
* 初始化 MediaPlayer
*/
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mediaPlayer.setAudioAttributes(attributes);
mediaPlayer.setOnPreparedListener(mp -> {
isPrepared = true;
Log.d(TAG, "播放器准备完成");
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
});
mediaPlayer.setOnCompletionListener(mp -> {
Log.d(TAG, "播放完成");
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
});
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Log.e(TAG, "播放错误: " + what + ", " + extra);
updatePlaybackState(PlaybackStateCompat.STATE_ERROR);
abandonAudioFocus();
return true;
});
}
// ==================== 音频焦点管理 ====================
/**
* 申请音频焦点
*/
private boolean requestAudioFocus() {
if (hasAudioFocus) {
return true;
}
int result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
result = audioManager.requestAudioFocus(audioFocusRequest);
} else {
// 兼容旧版本
result = audioManager.requestAudioFocus(
this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
}
hasAudioFocus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
if (hasAudioFocus) {
Log.d(TAG, "成功获得音频焦点");
} else {
Log.w(TAG, "获取音频焦点失败");
}
return hasAudioFocus;
}
/**
* 释放音频焦点
*/
private void abandonAudioFocus() {
if (hasAudioFocus) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
audioManager.abandonAudioFocusRequest(audioFocusRequest);
} else {
audioManager.abandonAudioFocus(this);
}
hasAudioFocus = false;
Log.d(TAG, "已释放音频焦点");
}
}
/**
* 音频焦点变化回调
*/
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "音频焦点变化: " + getFocusChangeName(focusChange));
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 获得焦点:恢复播放或音量
handleFocusGain();
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 永久失去焦点:停止播放
handleFocusLoss();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点:暂停播放
handleTransientFocusLoss();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 允许降音:降低音量
// 通常音频框架配置降音策略,APP无需处理
handleDuckingFocusLoss();
break;
}
}
private void handleFocusGain() {
hasAudioFocus = true;
if (resumeAfterFocusLoss && isPrepared) {
// 之前因焦点丢失而暂停,现在恢复播放
mediaPlayer.start();
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);
resumeAfterFocusLoss = false;
} else {
// 恢复正常音量
mediaPlayer.setVolume(1.0f, 1.0f);
}
}
private void handleFocusLoss() {
hasAudioFocus = false;
stopInternal();
abandonAudioFocus();
}
private void handleTransientFocusLoss() {
if (isPrepared && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
resumeAfterFocusLoss = true;
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
}
}
private void handleDuckingFocusLoss() {
if (isPrepared) {
// 降低音量到30%
mediaPlayer.setVolume(0.3f, 0.3f);
}
}
private String getFocusChangeName(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: return "GAIN";
case AudioManager.AUDIOFOCUS_LOSS: return "LOSS";
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: return "LOSS_TRANSIENT";
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: return "CAN_DUCK";
default: return "UNKNOWN";
}
}
// ==================== 播放控制 ====================
/**
* 加载媒体文件
*/
public void loadMedia(Uri uri, String title, String artist) {
try {
resetMediaPlayer();
mediaPlayer.setDataSource(this, uri);
mediaPlayer.prepareAsync();
// 更新元数据
MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.build();
mediaSession.setMetadata(metadata);
} catch (IOException e) {
Log.e(TAG, "加载媒体失败: " + e.getMessage());
updatePlaybackState(PlaybackStateCompat.STATE_ERROR);
}
}
/**
* 处理物理按键事件(方向盘/耳机按键)
*/
private boolean handleKeyEvent(KeyEvent keyEvent) {
int keyCode = keyEvent.getKeyCode();
Log.d(TAG, "keyCode: " + KeyEvent.keyCodeToString(keyCode));
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY:
play();
return true;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
pause();
return true;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
togglePlayPause();
return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
stop();
return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
skipToNext();
return true;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
skipToPrevious();
return true;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
fastForward();
return true;
case KeyEvent.KEYCODE_MEDIA_REWIND:
rewind();
return true;
default:
return false;
}
}
/**
* 内部播放方法(已检查音频焦点)
*/
private void playInternal() {
if (!isPrepared) {
Log.w(TAG, "播放器未准备就绪");
return;
}
try {
mediaPlayer.start();
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);
} catch (IllegalStateException e) {
Log.e(TAG, "播放失败: " + e.getMessage());
}
}
/**
* 公开播放方法
*/
public void play() {
if (requestAudioFocus()) {
playInternal();
}
}
/**
* 内部暂停方法
*/
private void pauseInternal() {
if (!isPrepared || !mediaPlayer.isPlaying()) {
return;
}
mediaPlayer.pause();
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
}
/**
* 公开暂停方法
*/
public void pause() {
pauseInternal();
// 注意:暂停时不释放音频焦点,以便快速恢复
}
/**
* 停止播放
*/
private void stopInternal() {
if (!isPrepared) {
return;
}
mediaPlayer.stop();
resetMediaPlayer();
updatePlaybackState(PlaybackStateCompat.STATE_STOPPED);
}
public void stop() {
stopInternal();
abandonAudioFocus();
}
public void seekTo(long positionMs) {
if (!isPrepared) return;
try {
mediaPlayer.seekTo((int) positionMs);
} catch (IllegalStateException e) {
Log.e(TAG, "定位失败: " + e.getMessage());
}
}
// ==================== 状态更新 ====================
private void updatePlaybackState(int state) {
currentState = state;
long position = isPrepared ? mediaPlayer.getCurrentPosition() : 0;
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder()
.setActions(getAvailableActions())
.setState(state, position, 1.0f);
if (state == PlaybackStateCompat.STATE_ERROR) {
builder.setErrorMessage("播放错误");
}
mediaSession.setPlaybackState(builder.build());
Log.d(TAG, "播放状态: " + getStateName(state) +
", 焦点状态: " + (hasAudioFocus ? "有焦点" : "无焦点"));
}
private long getAvailableActions() {
long actions = PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PAUSE |
PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_SEEK_TO;
if (currentState == PlaybackStateCompat.STATE_PLAYING) {
actions |= PlaybackStateCompat.ACTION_PAUSE;
} else if (currentState == PlaybackStateCompat.STATE_PAUSED ||
currentState == PlaybackStateCompat.STATE_STOPPED) {
actions |= PlaybackStateCompat.ACTION_PLAY;
}
return actions;
}
private void resetMediaPlayer() {
if (mediaPlayer != null) {
mediaPlayer.reset();
isPrepared = false;
}
}
private String getStateName(int state) {
switch (state) {
case PlaybackStateCompat.STATE_PLAYING: return "播放中";
case PlaybackStateCompat.STATE_PAUSED: return "已暂停";
case PlaybackStateCompat.STATE_STOPPED: return "已停止";
case PlaybackStateCompat.STATE_ERROR: return "错误";
default: return "未知";
}
}
// ==================== 服务生命周期 ====================
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
mediaSession.handleMediaKeyEvent(intent);
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "服务销毁");
stopInternal();
abandonAudioFocus();
if (mediaPlayer != null) {
mediaPlayer.release();
}
if (mediaSession != null) {
mediaSession.release();
}
}
// ==================== 公共方法 ====================
public MediaSessionCompat.Token getSessionToken() {
return mediaSession.getSessionToken();
}
public int getCurrentState() {
return currentState;
}
public boolean hasAudioFocus() {
return hasAudioFocus;
}
}
客户端 (MusicPlayerActivity.java)
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MusicPlayerActivity extends AppCompatActivity {
private static final String TAG = "MusicPlayerActivity";
private TextView tvStatus, tvTitle, tvArtist, tvPosition;
private Button btnPlayPause, btnStop, btnNext, btnPrev;
private MusicService musicService;
private MediaControllerCompat mediaController;
private MediaControllerCallback controllerCallback;
private Handler uiHandler = new Handler(Looper.getMainLooper());
private boolean isBound = false;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.LocalBinder binder = (MusicService.LocalBinder) service;
musicService = binder.getService();
isBound = true;
setupMediaController();
// 加载并播放音乐
Uri musicUri = Uri.parse("file:///sdcard/Music/test.mp3");
musicService.loadMedia(musicUri, "测试歌曲", "测试歌手");
musicService.play();
updateUI();
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
musicService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
initViews();
bindMusicService();
}
private void initViews() {
tvStatus = findViewById(R.id.tv_status);
tvTitle = findViewById(R.id.tv_title);
tvArtist = findViewById(R.id.tv_artist);
tvPosition = findViewById(R.id.tv_position);
btnPlayPause = findViewById(R.id.btn_play_pause);
btnStop = findViewById(R.id.btn_stop);
btnNext = findViewById(R.id.btn_next);
btnPrev = findViewById(R.id.btn_prev);
btnPlayPause.setOnClickListener(v -> togglePlayPause());
btnStop.setOnClickListener(v -> stop());
btnNext.setOnClickListener(v -> skipToNext());
btnPrev.setOnClickListener(v -> skipToPrevious());
}
// 播放控制方法
private void togglePlayPause() {
if (mediaController == null) return;
PlaybackStateCompat state = mediaController.getPlaybackState();
if (state != null) {
if (state.getState() == PlaybackStateCompat.STATE_PLAYING) {
mediaController.getTransportControls().pause();
} else {
mediaController.getTransportControls().play();
}
}
}
private void stop() {
if (mediaController != null) {
mediaController.getTransportControls().stop();
}
}
private void skipToNext() {
if (mediaController != null) {
mediaController.getTransportControls().skipToNext();
}
}
private void skipToPrevious() {
if (mediaController != null) {
mediaController.getTransportControls().skipToPrevious();
}
}
private void bindMusicService() {
Intent intent = new Intent(this, MusicService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
private void setupMediaController() {
try {
MediaSessionCompat.Token token = musicService.getSessionToken();
mediaController = new MediaControllerCompat(this, token);
MediaControllerCompat.setMediaController(this, mediaController);
controllerCallback = new MediaControllerCallback();
mediaController.registerCallback(controllerCallback);
} catch (Exception e) {
Log.e(TAG, "Failed to setup media controller: " + e.getMessage());
}
}
// UI 更新方法
private void updateUI() {
if (musicService == null) return;
uiHandler.post(() -> {
// 更新播放状态
int state = musicService.getCurrentState();
String statusText = "";
switch (state) {
case PlaybackStateCompat.STATE_PLAYING:
statusText = "正在播放";
btnPlayPause.setText("暂停");
break;
case PlaybackStateCompat.STATE_PAUSED:
statusText = "已暂停";
btnPlayPause.setText("播放");
break;
case PlaybackStateCompat.STATE_STOPPED:
statusText = "已停止";
btnPlayPause.setText("播放");
break;
case PlaybackStateCompat.STATE_ERROR:
statusText = "播放错误";
break;
}
tvStatus.setText(statusText);
// 更新进度
long position = musicService.getCurrentPosition();
tvPosition.setText(formatDuration(position));
});
}
private String formatDuration(long millis) {
long seconds = millis / 1000;
long minutes = seconds / 60;
seconds = seconds % 60;
return String.format("%02d:%02d", minutes, seconds);
}
// MediaController 回调类
private class MediaControllerCallback extends MediaControllerCompat.Callback {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
Log.d(TAG, "Playback state changed: " + state.getState());
updateUI();
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
super.onMetadataChanged(metadata);
if (metadata != null) {
uiHandler.post(() -> {
String title = metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
String artist = metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
tvTitle.setText(title != null ? title : "未知标题");
tvArtist.setText(artist != null ? artist : "未知歌手");
});
}
}
@Override
public void onSessionDestroyed() {
super.onSessionDestroyed();
Log.w(TAG, "Session destroyed");
finish();
}
}
@Override
protected void onResume() {
super.onResume();
if (mediaController != null && controllerCallback != null) {
mediaController.registerCallback(controllerCallback);
}
}
@Override
protected void onPause() {
super.onPause();
if (mediaController != null && controllerCallback != null) {
mediaController.unregisterCallback(controllerCallback);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(serviceConnection);
}
}
}
布局文件 (activity_player.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未连接"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="标题"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="歌手"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="00:00"
android:textSize="12sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_prev"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="上一首" />
<Button
android:id="@+id/btn_play_pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="播放/暂停" />
<Button
android:id="@+id/btn_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="停止" />
<Button
android:id="@+id/btn_next"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="下一首" />
</LinearLayout>
</LinearLayout>
配置文件(AndroidManifest.xml)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.musicplayer">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
<!-- 播放服务 -->
<service
android:name=".MusicPlayerService"
android:foregroundServiceType="mediaPlayback"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<!-- 播放界面 -->
<activity android:name=".MusicPlayerActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>