核心概念
要实现网络音乐播放,你需要理解以下几个关键组件和技术:

-
音频流:网络音乐不是下载整个文件再播放,而是边下载边播放,这种数据流叫做“流式音频”,常见的格式有:
- MP3: 最广泛支持的格式。
- AAC: 效率通常比 MP3 更高,音质更好。
- FLAC/APE: 无损压缩格式,文件较大,但音质极佳。
- HLS (HTTP Live Streaming): 苹果主导的流媒体协议,将音视频切分成小的
.ts文件,通过一个.m3u8播放列表文件来管理,非常适合直播和自适应码率播放。
-
网络请求:你需要从服务器获取音频数据流,Android 提供了多种网络请求库,如
HttpURLConnection(原生)、OkHttp(强烈推荐,功能强大且高效)。 -
音频播放:Android 系统提供了强大的
MediaPlayer和更现代的ExoPlayer来播放音频。- MediaPlayer: Android SDK 自带的播放器,简单易用,但功能相对固定,定制性差,在处理复杂网络流(如 HLS)时不够稳定。
- ExoPlayer: Google 官方推荐的开源播放器,基于
MediaPlayer的MediaPlayer2构建,它高度可定制、性能优异、功能强大(支持 HLS, DASH, 自适应码率等),是专业应用的首选。
-
UI 交互:你需要一个用户界面来显示音乐信息(歌名、歌手、封面)、控制播放(播放/暂停、上一首/下一首、进度条拖动等)。
(图片来源网络,侵删)
使用 MediaPlayer (简单入门)
MediaPlayer 适合实现简单的播放功能,几行代码就能搞定。
步骤:
-
添加网络权限:在
AndroidManifest.xml中声明网络访问权限。<uses-permission android:name="android.permission.INTERNET" />
-
布局文件 (
activity_main.xml):添加一个简单的 UI。<LinearLayout ...> <ImageView android:id="@+id/albumArt" android:layout_width="200dp" android:layout_height="200dp" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/titleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Song Title" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/prevButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Prev" /> <Button android:id="@+id/playPauseButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Play" /> <Button android:id="@+id/nextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" /> </LinearLayout> </LinearLayout> -
Activity 代码 (
MainActivity.kt):
(图片来源网络,侵删)import android.media.MediaPlayer import android.net.Uri import android.os.Bundle import android.widget.Button import android.widget.SeekBar import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { private lateinit var mediaPlayer: MediaPlayer private lateinit var playPauseButton: Button private lateinit var seekBar: SeekBar private lateinit var titleTextView: TextView // 示例音乐URL(请替换为你自己的URL) private val musicUrl = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) playPauseButton = findViewById(R.id.playPauseButton) seekBar = findViewById(R.id.seekBar) titleTextView = findViewById(R.id.titleTextView) titleTextView.text = "正在播放..." mediaPlayer = MediaPlayer().apply { setDataSource(this@MainActivity, Uri.parse(musicUrl)) // 异步准备,避免阻塞UI线程 prepareAsync() } // 监听准备完成 mediaPlayer.setOnPreparedListener { mp -> titleTextView.text = "SoundHelix Song 1" seekBar.max = mp.duration playPauseButton.text = "Play" // 开始播放 mp.start() } // 监听播放完成 mediaPlayer.setOnCompletionListener { mp -> playPauseButton.text = "Play" seekBar.progress = 0 } // 播放/暂停按钮点击事件 playPauseButton.setOnClickListener { if (mediaPlayer.isPlaying) { mediaPlayer.pause() playPauseButton.text = "Play" } else { mediaPlayer.start() playPauseButton.text = "Pause" } } // 进度条拖动事件 seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { // 如果是由用户拖动引起的 if (fromUser) { mediaPlayer.seekTo(progress) } } // 其他两个方法可以留空 override fun onStartTrackingTouch(seekBar: SeekBar?) {} override fun onStopTrackingTouch(seekBar: SeekBar?) {} }) // 使用Handler来更新进度条 val handler = android.os.Handler(mainLooper) val updateSeekBar = object : Runnable { override fun run() { if (mediaPlayer.isPlaying) { seekBar.progress = mediaPlayer.currentPosition } handler.postDelayed(this, 1000) // 每秒更新一次 } } handler.post(updateSeekBar) } override fun onDestroy() { super.onDestroy() // 释放资源,防止内存泄漏 mediaPlayer.release() } }
使用 ExoPlayer (专业推荐)
ExoPlayer 功能强大,是构建高质量音乐播放器的最佳选择,它能更好地处理网络状况、缓存和不同格式的流。
步骤:
-
添加依赖:在
app/build.gradle文件中添加 ExoPlayer 依赖。dependencies { // ExoPlayer Core implementation "androidx.media3:media3-exoplayer:1.3.1" // 请使用最新版本 // ExoPlayer UI 组件 (可选,用于快速构建播放器UI) implementation "androidx.media3:media3-ui-widget:1.3.1" // ExoPlayer HLS 支持 (如果需要播放m3u8文件) implementation "androidx.media3:media3-exoplayer-hls:1.3.1" } -
布局文件 (
activity_main.xml):我们可以使用 ExoPlayer 提供的PlayerView,它集成了播放、进度条、缓冲等UI。<androidx.media3.ui.PlayerView android:id="@+id/player_view" android:layout_width="match_parent" android:layout_height="match_parent" app:use_controller="true" // 显示控制器 app:show_buffering="when_playing" /> <!-- 显示缓冲指示器 --> -
Activity 代码 (
MainActivity.kt):import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView class MainActivity : AppCompatActivity() { private lateinit var exoPlayer: ExoPlayer private lateinit var playerView: PlayerView // 示例音乐URL (可以是MP3或m3u8) private val musicUrl = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" // private val hlsUrl = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) playerView = findViewById(R.id.player_view) // 1. 创建 ExoPlayer 实例 exoPlayer = ExoPlayer.Builder(this).build().also { player -> // 2. 将播放器与 PlayerView 关联 playerView.player = player // 3. 创建 MediaItem val mediaItem = MediaItem.fromUri(musicUrl) // 4. 将 MediaItem 设置给播放器 player.setMediaItem(mediaItem) // 5. 准备并播放 player.prepare() player.playWhenReady = true // 自动播放 } // 监听播放状态 exoPlayer.addListener(object : Player.Listener { override fun onPlaybackStateChanged(playbackState: Int) { when (playbackState) { Player.STATE_BUFFERING -> { // 正在缓冲 } Player.STATE_READY -> { // 准备完成,可以播放 } Player.STATE_ENDED -> { // 播放结束 } } } }) } override fun onStart() { super.onStart() if (exoPlayer.playWhenReady) { exoPlayer.play() } } override fun onStop() { super.onStop() exoPlayer.pause() } override fun onDestroy() { super.onDestroy() // 释放 ExoPlayer 资源 exoPlayer.release() } }
进阶与最佳实践
-
后台播放:当用户退出应用或锁屏时,音乐应该继续播放,这需要使用
MediaPlayer或ExoPlayer的Foreground Service。- 创建
Service:继承Service并在onCreate中初始化你的播放器。 - 启动为前台服务:在
onStartCommand中调用startForeground(),并显示一个持续的通知,这是 Android 8.0 (Oreo) 及以上版本的要求。 - 绑定 Service:在你的
Activity中,通过ServiceConnection来与Service交互,从而控制播放器的播放、暂停等。
- 创建
-
音频焦点:当用户接听电话、使用导航或另一个音乐应用播放时,你的应用应该暂停播放并在获得焦点时恢复。
- 实现
AudioManager.OnAudioFocusChangeListener。 - 在播放前请求音频焦点:
audioManager.requestAudioFocus(...) - 在释放时放弃焦点:
audioManager.abandonAudioFocus(...)
- 实现
-
播放列表管理:你需要一个数据结构(如
List<MediaItem>)来管理多首歌曲,并实现上一首、下一首的逻辑。 -
缓存:对于需要反复收听的音频,使用
ExoPlayer的CacheDataSource可以显著提升用户体验,减少重复下载,并实现离线播放。 -
UI 状态管理:使用
ViewModel和LiveData或StateFlow来管理播放状态(播放/暂停、当前歌曲、播放进度等),这样即使屏幕旋转,UI 状态也能得到保留。
总结对比
| 特性 | MediaPlayer |
ExoPlayer |
|---|---|---|
| 易用性 | 非常简单,几行代码即可运行 | 相对复杂,需要更多设置 |
| 性能 | 一般,对网络状况不敏感 | 优秀,有自适应码率、缓存等优化 |
| 功能 | 基础播放功能 | 功能极其丰富,支持 HLS, DASH, 自定义渲染器等 |
| 定制性 | 差,UI 和行为难以深度定制 | 极高,可以自定义UI、播放器行为 |
| 推荐场景 | 简单的、一次性的播放需求 | 音乐App、视频App等生产环境应用 |
- 如果你只是想快速实现一个简单的网络音频播放功能,
MediaPlayer足够用。 - 如果你正在开发一个正式的音乐播放器,或者对性能、稳定性和功能有较高要求,强烈推荐使用
ExoPlayer,它是 Android 平台现代媒体播放的黄金标准。
