目录
- 核心概念
- 视频播放流程
- 关键技术点
- 主流播放器库推荐
- ExoPlayer (Google 官方首选)
- IjkPlayer (Bilibili 开源,功能强大)
- Vitamio (老牌播放器,兼容性好)
- 实战:使用 ExoPlayer 播放网络视频
- 步骤 1: 添加依赖
- 步骤 2: 添加网络权限
- 步骤 3: 布局文件
- 步骤 4: Java/Kotlin 代码实现
- 步骤 5: 处理生命周期
- 高级功能与优化
- 播放控制(播放、暂停、进度条、音量)
- 全屏切换
- 缓存策略
- 清晰度切换
- 硬件解码
- 自定义 UI
- 常见问题与解决方案
- 视频无法播放(黑屏、无声音)
- 播放卡顿、延迟高
- 内存泄漏
- 横竖屏切换问题
- 总结与最佳实践
核心概念
视频播放流程
一个典型的网络视频播放流程如下:

- 用户操作:用户点击播放按钮。
- UI 更新:界面显示加载动画。
- 网络请求:App 通过
HttpURLConnection或 OkHttp 等库向视频服务器请求视频数据。 - 数据下载:服务器返回视频流(通常是 MP4, M3U8 等)。
- 数据解码:播放器引擎(如 ExoPlayer)将下载到的视频数据解码成图像和声音,这个过程可以是软件解码(使用 CPU,兼容性好但性能差)或硬件解码(使用 GPU/专用芯片,性能高)。
- 渲染显示:解码后的视频帧被绘制到屏幕上,音频通过扬声器播放。
关键技术点
- 协议:常见的视频流协议有 HTTP (Progressive Download)、HLS (HTTP Live Streaming, .m3u8)、DASH 等,HLS 和 DASH 支持多种清晰度,是主流选择。
- 解码:硬件解码是保证流畅播放的关键,现代播放器库会自动尝试使用硬件解码。
- 缓存:将已下载的视频片段存储在本地,可以避免重复下载,实现“秒开”和离线播放,并节省流量。
主流播放器库推荐
| 库名 | 开发者 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| ExoPlayer | Google 官方 | 功能强大、高度可定制、与 Android 生态集成好、持续更新、支持所有主流格式和协议 | 学习曲线稍陡,集成初期代码量稍多 | 新项目首选,特别是需要高度定制和最新特性的 App |
| IjkPlayer | Bilibili | 性能优异、功能丰富(如弹幕支持)、基于 FFmpeg,解码能力强大 | 停止维护已久,可能存在兼容性问题(如新 Android 版本) | 对性能有极致要求,或需要依赖 FFmpeg 特定功能的场景 |
| Vitamio | 非官方 | 兼容性极好,能播放很多其他播放器播不了的格式 | 停止维护已久,有广告(商业版),性能和架构相对老旧 | 维护老项目,或需要播放一些非常规格式视频的场景 |
对于 2025 年及以后的新项目,强烈推荐使用 ExoPlayer。
实战:使用 ExoPlayer 播放网络视频
下面我们以最主流的 ExoPlayer 为例,实现一个简单的网络视频播放器。
步骤 1: 添加依赖
在 build.gradle (Module: app) 文件中添加 ExoPlayer 的依赖。
dependencies {
// ExoPlayer 核心库
implementation 'androidx.media3:media3-exoplayer:1.3.1' // 请使用最新版本
// ExoPlayer UI 组件(提供了默认的播放控制条)
implementation 'androidx.media3:media3-ui:1.3.1'
// ExoPlayer HLS 支持(用于播放 .m3u8 文件)
implementation 'androidx.media3:media3-exoplayer-hls:1.3.1'
}
步骤 2: 添加网络权限
在 AndroidManifest.xml 文件中添加网络访问权限。

<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
...
</application>
</manifest>
步骤 3: 布局文件
在 activity_main.xml 中放置一个 PlayerView 组件,这是 ExoPlayer 提供的、集成了视频显示和基本控制 UI 的视图。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<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"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:use_controller="true":显示默认的播放控制条(播放/暂停、进度条、音量等)。app:show_buffering="when_playing":在播放时显示缓冲指示器。
步骤 4: Java/Kotlin 代码实现
在 MainActivity.kt 中初始化并配置 ExoPlayer。
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 player: ExoPlayer
private lateinit var playerView: PlayerView
// 替换成你的视频 URL
private val videoUrl = "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 实例
player = ExoPlayer.Builder(this).build()
// 2. 将 PlayerView 的 player 设置为 ExoPlayer 实例
playerView.player = player
// 3. 创建 MediaItem
val mediaItem = MediaItem.fromUri(videoUrl)
// 4. 将 MediaItem 设置给 player
player.setMediaItem(mediaItem)
// 5. 准备播放
player.prepare()
// 6. 开始播放
player.playWhenReady = true
}
}
步骤 5: 处理生命周期
这是最关键的一步! 必须在 Activity 的生命周期方法中正确地管理 ExoPlayer 的生命周期,否则会导致内存泄漏和崩溃。
override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
playerView.onResume()
}
}
override fun onResume() {
super.onResume()
if (Util.SDK_INT <= 23) {
playerView.onResume()
}
}
override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23) {
playerView.onPause()
}
}
override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23) {
playerView.onPause()
}
}
// 当 Activity 销毁时,必须释放 ExoPlayer 资源
override fun onDestroy() {
super.onDestroy()
player.release()
}
注意:
Util是androidx.media3.common.util.Util。(图片来源网络,侵删)
高级功能与优化
播放控制
- 播放/暂停:
player.playWhenReady = true/false - 跳转:
player.seekTo(position) - 监听播放状态:
player.addListener(object : Player.Listener { ... }),可以监听isPlaying,playbackState等变化。
全屏切换
PlayerView 自带全屏按钮,你也可以通过代码控制:
// 进入全屏 playerView.resizeMode = PlayerView.RESIZE_MODE_FILL // 退出全屏 playerView.resizeMode = PlayerView.RESIZE_MODE_FIT
实际应用中,通常需要监听屏幕方向变化,并配合 Activity 的 setRequestedOrientation() 来实现真正的全屏。
缓存策略
ExoPlayer 内置了强大的 DownloadTracker 和 Cache,你需要:
- 创建一个
SimpleCache实例。 - 使用
DefaultDataSource.Factory时,将CacheDataSource包装进去。 - 配置
DownloadService来在后台管理下载和缓存。
这是一个相对复杂的话题,官方文档有详细的示例。
清晰度切换
对于 HLS (.m3u8) 或 DASH 格式,一个 .m3u8 文件可以包含多个不同码率的视频流。
- 获取可用码率:解析
.m3u8文件,得到不同清晰度的MediaItem。 - 切换码率:使用
player.setMediaItem(newMediaItem, true)来切换,第二个参数true表示重置播放器。
// 假设你有一个更高清的 URL val highQualityUrl = "..." val highQualityMediaItem = MediaItem.fromUri(highQualityUrl) // 切换到高清 player.setMediaItem(highQualityMediaItem, true)
硬件解码
ExoPlayer 默认会优先尝试使用硬件解码,你可以通过 RenderersFactory 来强制或禁用特定类型的解码器,但通常不需要手动干预。
自定义 UI
PlayerView 的控制器 (PlayerControlView) 是可以高度自定义的,你可以:
- 隐藏默认控制器:
app:use_controller="false" - 完全自己写一个
View,通过playerView.player获取Player实例,然后监听其状态,手动控制 UI。
常见问题与解决方案
视频无法播放(黑屏、无声音)
- 检查 URL:确保视频地址有效,可以直接在浏览器中打开播放。
- 检查权限:确认
INTERNET权限已添加。 - 检查网络:模拟器或真机网络是否正常。
- 检查格式:确认 ExoPlayer 支持该视频格式(如 RTMP 可能需要额外配置)。
- 检查日志:查看 Logcat,
ExoPlayer通常会输出详细的错误信息,如DecoderInitException表示解码失败。
播放卡顿、延迟高
- 使用硬件解码:确保 ExoPlayer 默认行为生效,没有使用软件解码。
- 启用缓存:为视频启用缓存,减少网络请求。
- 优化网络:检查网络状况,Wi-Fi 通常比 4G/5G 更稳定。
- 降低清晰度:如果网络不佳,切换到更低码率的视频流。
内存泄漏
- 根本原因:在
onDestroy()之前,ExoPlayer实例或其监听器被其他对象(如静态变量)持有。 - 解决方案:
- 严格按照生命周期管理,在
onDestroy()中调用player.release()。 - 避免将
player实例或其引用存储在 Activity/Fragment 的生命周期之外(如单例模式中,除非你非常清楚自己在做什么)。 - 移除不再需要的监听器。
- 严格按照生命周期管理,在
横竖屏切换问题
- 问题:切换屏幕方向时,
Activity会重建,导致播放器重新初始化,播放中断。 - 解决方案:
- 配置 Activity:在
AndroidManifest.xml中为 Activity 添加android:configChanges="orientation|screenSize|keyboardHidden",这样,屏幕旋转时不会重建 Activity,而是调用onConfigurationChanged()。 - 处理配置变化:在
onConfigurationChanged()中,处理 UI 的旋转逻辑。 - 更推荐的方式:使用
ViewModel,将ExoPlayer实例存储在ViewModel中,当Activity重建时,ViewModel不会销毁,从而保留了播放器实例,实现无缝切换。
- 配置 Activity:在
总结与最佳实践
- 选择合适的播放器:优先选择 ExoPlayer,它代表了 Android 视频播放的未来。
- 管理生命周期:将
player.release()放在onDestroy()或onCleared()(ViewModel) 中是铁律。 - 使用 ViewModel:对于需要长期持有状态(如播放器实例)的 UI,
ViewModel是最佳实践,能完美处理屏幕旋转等配置变化。 - 拥抱官方文档:ExoPlayer 的官方文档非常详尽,遇到问题时,第一手资料永远是官方文档。
- 从简单开始:先实现一个能播放视频的基本功能,再逐步叠加高级功能(如缓存、清晰度切换、自定义 UI)。
希望这份指南能帮助你顺利地在 Android App 中实现网络视频播放功能!

