Android实现后台播放音乐(附带源码)

一、项目介绍

1.1 背景与意义

在音乐或音频类应用中,用户往往希望在切换到其他应用,甚至按下 Home 键时,音乐仍能持续播放。Android 默认情况下,当应用退到后台后,Activity 的生命周期会被暂停或销毁,如果不做特殊处理,MediaPlayer 会随之停止播放。为确保用户体验,需要将播放逻辑放到后台可持续运行的组件——Service 中,并结合前台服务(Foreground Service)和Notification,保证在系统资源紧张时仍能保活。

典型场景包括:

  • 音乐播放器 App:用户在听歌时切换到微信或浏览器,不希望音乐中断。

  • 播客/有声读物:听小说或访谈,放到后台后继续收听;

  • 导航或直播:语音播报或直播音频退到后台也要持续播放。

1.2 项目目标

  • 构建一个可在后台持续播放音乐的 Demo;

  • 播放逻辑放在 Service 中,并以前台服务形式运行;

  • 在通知栏显示播放进度、控制按钮(播放/暂停、停止);

  • 支持在 Activity 中一键启动/停止服务;

  • 所有代码整合到一个代码块,通过注释区分不同文件,注释要非常详细;

  • 拓展相关知识:Android 后台限制、前台服务、Notification Channel、Audio Focus 处理等。


二、相关知识拓展

  1. Service 与前台服务

    • 普通 Service 在系统资源紧张时可能被系统回收;

    • 前台服务(调用 startForeground())绑定一个常驻通知,能显著降低被回收风险,并且适用于播放、导航、Step Counter 等持续任务。

  2. Android 8.0+ 后台执行限制

    • 自 API 26 起,后台启动 Service 受到限制,需要使用 startForegroundService() 启动,Service 在 5 秒内必须调用 startForeground()

  3. Notification Channel

    • 自 API 26 起,创建通知前必须先注册渠道(Channel),否则通知不会显示;

  4. MediaPlayer 与 Audio Focus

    • 正确获取并监听 AudioManager 的音频焦点,处理来电、中断、多媒体同时播放等场景;

  5. MediaSession & MediaStyle Notification(可扩展)

    • 使用 MediaSession 对外暴露控制接口,在锁屏、蓝牙设备、Android Auto 上也能使用通知栏媒体控件。


三、实现思路

  1. AndroidManifest.xml

    • 申请 FOREGROUND_SERVICE 权限;

    • 声明 MusicService

  2. 编写 MusicService

    • 继承自 Service,持有 MediaPlayer 实例;

    • onStartCommand() 中初始化并开始播放;

    • 调用 startForeground() 并创建一个带播放控制按钮的通知;

    • 通过 Notification 的 Action 点击 PendingIntent 来控制播放/暂停与停止。

  3. 编写 MainActivity

    • 提供两个按钮:开始播放(启动前台服务)与停止播放(停止服务);

    • onDestroy() 中可解除绑定或停止服务。

  4. Notification 与交互

    • 建立 Notification Channel;

    • 构建 MediaStyle 通知,添加“暂停/播放”、“停止”按钮;

    • 在 Service 中接收 Intent Action 并执行对应命令。

  5. 处理音频焦点(可选)

    • 在 Service 中向 AudioManager 请求焦点,并监听焦点变化,做“暂退”或“停止”处理。


四、完整代码整合(含详细注释)

// ==================== File: AndroidManifest.xml ====================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.backgroundmusic">

    <!-- 8.0+ 启动前台服务需声明该权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="后台音乐播放"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">

        <!-- 活动入口 -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- 后台播放服务 -->
        <service
            android:name=".MusicService"
            android:exported="false"/>
    </application>
</manifest>


// ==================== File: MainActivity.java ====================
package com.example.backgroundmusic;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

/**
 * MainActivity:提供启动、停止后台音乐播放的按钮
 */
public class MainActivity extends AppCompatActivity {

    private Button btnStart, btnStop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // 加载布局

        btnStart = findViewById(R.id.btn_start);
        btnStop  = findViewById(R.id.btn_stop);

        // 点击“开始播放”,启动前台服务
        btnStart.setOnClickListener(v -> {
            Intent intent = new Intent(this, MusicService.class);
            // API26+ 要用 startForegroundService
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(intent);
            } else {
                startService(intent);
            }
        });

        // 点击“停止播放”,停止服务
        btnStop.setOnClickListener(v -> {
            Intent intent = new Intent(this, MusicService.class);
            stopService(intent);
        });
    }
}


// ==================== File: activity_main.xml ====================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="24dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始播放音乐"/>

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止播放音乐"
        android:layout_marginTop="16dp"/>
</LinearLayout>


// ==================== File: MusicService.java ====================
package com.example.backgroundmusic;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

/**
 * MusicService:在前台服务中使用 MediaPlayer 播放音乐
 */
public class MusicService extends Service {

    // 通知通道与通知 ID
    private static final String CHANNEL_ID = "music_play_channel";
    private static final int    NOTIF_ID  = 1001;

    // 通知动作字符串,用于区分点击事件
    private static final String ACTION_PLAY  = "ACTION_PLAY";
    private static final String ACTION_PAUSE = "ACTION_PAUSE";
    private static final String ACTION_STOP  = "ACTION_STOP";

    private MediaPlayer mediaPlayer;
    private boolean     isPlaying = false;

    @Override
    public void onCreate() {
        super.onCreate();
        // 1. 创建通知渠道(Android 8.0+)
        createNotificationChannel();
        // 2. 初始化 MediaPlayer
        initMediaPlayer();
    }

    /**
     * 在 startService/startForegroundService 后回调
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 区分点击通知的 Action
        if (intent != null && intent.getAction() != null) {
            switch (intent.getAction()) {
                case ACTION_PLAY:
                    resumeMusic();
                    break;
                case ACTION_PAUSE:
                    pauseMusic();
                    break;
                case ACTION_STOP:
                    stopSelf(); // 停止 Service
                    return START_NOT_STICKY;
            }
        } else {
            // 首次启动,直接开始播放
            startMusic();
        }

        // 每次 onStartCommand 都需要调用 startForeground,保持前台状态
        startForeground(NOTIF_ID, buildNotification());
        // 如果被系统杀掉,不再自动重启
        return START_NOT_STICKY;
    }

    /**
     * 初始化 MediaPlayer,并设置音频属性
     */
    private void initMediaPlayer() {
        mediaPlayer = MediaPlayer.create(this, R.raw.sample_music);
        mediaPlayer.setLooping(true); // 循环播放
        // 适配 API21+ 的音频属性
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mediaPlayer.setAudioAttributes(
                new AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .build()
            );
        } else {
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        }
    }

    /** 开始播放并更新状态 */
    private void startMusic() {
        if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
            mediaPlayer.start();
            isPlaying = true;
        }
    }

    /** 暂停播放并更新状态 */
    private void pauseMusic() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
            isPlaying = false;
        }
    }

    /** 恢复播放并更新状态 */
    private void resumeMusic() {
        if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
            mediaPlayer.start();
            isPlaying = true;
        }
    }

    /** 构建前台服务通知,包含播放/暂停/停止按钮 */
    private Notification buildNotification() {
        // 点击通知主体,返回 MainActivity
        PendingIntent mainIntent = PendingIntent.getActivity(
            this, 0,
            new Intent(this, MainActivity.class),
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // 播放/暂停按钮 Intent
        Intent playPauseIntent = new Intent(this, MusicService.class)
            .setAction(isPlaying ? ACTION_PAUSE : ACTION_PLAY);
        PendingIntent ppPending = PendingIntent.getService(
            this, 1, playPauseIntent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // 停止按钮 Intent
        Intent stopIntent = new Intent(this, MusicService.class)
            .setAction(ACTION_STOP);
        PendingIntent stopPending = PendingIntent.getService(
            this, 2, stopIntent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // 构造 Notification
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("正在播放音乐")
            .setContentText(isPlaying ? "点击暂停" : "点击播放")
            .setSmallIcon(R.drawable.ic_music_note)
            .setContentIntent(mainIntent) // 点击通知主体
            .addAction(isPlaying ? R.drawable.ic_pause : R.drawable.ic_play,
                       isPlaying ? "暂停" : "播放", ppPending)
            .addAction(R.drawable.ic_stop, "停止", stopPending)
            // 使用 MediaStyle,支持在锁屏及车载展示
            .setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
                .setShowActionsInCompactView(0, 1));

        return builder.build();
    }

    /** 创建通知渠道(Android 8.0+) */
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel chan = new NotificationChannel(
                CHANNEL_ID,
                "音乐播放服务",
                NotificationManager.IMPORTANCE_LOW
            );
            chan.setDescription("用于在后台播放音乐的前台服务");
            NotificationManager mgr = getSystemService(NotificationManager.class);
            if (mgr != null) {
                mgr.createNotificationChannel(chan);
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 释放 MediaPlayer
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        // 取消前台状态与通知
        stopForeground(true);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 不提供绑定,此处返回 null
        return null;
    }
}

说明: 需在 res/raw/ 目录下添加一个 sample_music.mp3 作为示例音乐文件,并在 res/drawable/ 下提供 ic_music_noteic_playic_pauseic_stop 等图标。


五、代码解读(方法作用说明)

  • AndroidManifest.xml

    • 声明 FOREGROUND_SERVICE 权限;

    • 注册 MusicService,使系统识别该 Service。

  • MainActivity

    • btnStart 点击调用 startForegroundService()(API26+)或 startService()

    • btnStop 点击调用 stopService() 停止播放。

  • MusicService.onCreate()

    • 调用 createNotificationChannel() 为通知创建渠道;

    • 调用 initMediaPlayer() 初始化 MediaPlayer,并设置音频属性与循环模式。

  • MusicService.onStartCommand()`

    • 根据 Intent 的 getAction() 判断是“首次启动”“播放/暂停”还是“停止”;

    • 调用相应的 startMusic()pauseMusic()stopSelf()

    • 每次都需调用 startForeground() 并传入最新状态的通知。

  • buildNotification()

    • 构建跳回主界面与播放/暂停/停止的 PendingIntent

    • 使用 NotificationCompat.MediaStyle(),将控制按钮在紧凑视图中展示;

    • setContentIntent() 让用户点击通知主体可返回应用。

  • onDestroy()

    • 停止并释放 MediaPlayer

    • 调用 stopForeground(true) 取消前台服务并移除通知。


六、项目总结与拓展

6.1 实现效果回顾

  • 将播放逻辑放入 Service,并以前台服务形式运行,确保应用退到后台时音乐不被系统回收;

  • 通知栏提供播放/暂停/停止按钮,用户可直接从通知控制;

  • 兼容 Android 8.0+ 的后台执行限制与通知渠道要求。

6.2 常见坑与注意

  1. 前台服务未及时调用 startForeground:API26+ 调用 startForegroundService() 后,Service 必须在 5 秒内调用 startForeground(),否则系统会杀掉 Service;

  2. 缺少通知渠道:Android8.0+ 若不创建通知渠道,通知无法显示;

  3. PendingIntent FLAG:使用 FLAG_IMMUTABLEFLAG_MUTABLE 以满足 Android12+ 的安全要求;

  4. 音频焦点管理:当前未处理来电或其他应用夺取焦点时的暂停逻辑,生产环境需注册 AudioManager.OnAudioFocusChangeListener

6.3 可扩展方向

  • MediaSession & MediaStyle 深度集成:让通知支持更多控制按钮,并与蓝牙、锁屏、Android Auto 等设备互通;

  • 播放队列与进度控制:在通知上展示进度条,并能拖动进度;

  • 使用 ExoPlayer:替代 MediaPlayer,更强大的缓存与格式兼容能力;

  • 音频焦点 & 媒体按钮:监听耳机按键、来电状态,实现更好的用户体验;

  • 歌词展示 &可视化:结合 Visualizer API,实现歌词滚动或音频频谱可视化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值