1. Android多媒体开发实战:从通知到音视频播放
作为一名有五年Android开发经验的工程师,我深知多媒体功能在移动应用中的重要性。记得第一次实现拍照功能时,因为没处理好权限问题导致应用崩溃,这个教训让我深刻理解了Android多媒体开发的复杂性。本文将带你系统掌握Android四大核心多媒体功能的实现,包含最新Android 13的适配方案。
2. 通知功能深度解析与实现
2.1 通知系统的演进与设计哲学
Android通知系统从8.0开始引入了渠道概念,这不仅是技术实现的变化,更反映了Google对用户体验的重视。我曾在用户调研中发现,超过60%的用户会因通知管理不当而卸载应用。
通知渠道的核心设计理念是:
- 让用户掌握控制权
- 区分不同优先级的信息
- 提供一致的视觉体验
2.2 通知渠道的创建与管理
创建通知渠道时需要特别注意:
kotlin复制fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(
"chat_messages",
"聊天消息",
importance
).apply {
description = "接收好友聊天消息"
enableVibration(true)
vibrationPattern = longArrayOf(100, 200, 300)
setShowBadge(true)
}
val notificationManager = context.getSystemService(
NotificationManager::class.java
)
notificationManager.createNotificationChannel(channel)
}
}
重要提示:渠道一旦创建,大部分属性将无法通过代码修改,用户只能在系统设置中调整。因此建议在Application的onCreate中初始化所有渠道。
2.3 Android 13通知权限适配实战
Android 13引入了POST_NOTIFICATIONS运行时权限,我们的适配策略应该是:
- 在AndroidManifest.xml中声明权限
- 检查权限状态:
kotlin复制fun checkNotificationPermission(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
} else {
true
}
}
- 优雅地请求权限并处理拒绝情况
3. 相机与相册功能实现
3.1 相机调用最佳实践
调用系统相机拍照时,最常见的坑是:
- 直接使用Intent返回的缩略图(质量差)
- 未处理存储权限
- 未考虑不同厂商设备的兼容性
推荐的做法是:
kotlin复制fun takePhoto(activity: Activity, requestCode: Int) {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// 创建临时文件
val photoFile = createImageFile(activity)
val photoUri = FileProvider.getUriForFile(
activity,
"${activity.packageName}.fileprovider",
photoFile
)
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
activity.startActivityForResult(intent, requestCode)
}
private fun createImageFile(context: Context): File {
val timeStamp = SimpleDateFormat(
"yyyyMMdd_HHmmss",
Locale.getDefault()
).format(Date())
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_",
".jpg",
storageDir
)
}
3.2 相册选择的多版本适配
从相册选择媒体文件时,Android 13的权限变化最大:
kotlin复制fun checkMediaPermissions(activity: Activity): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(
activity,
Manifest.permission.READ_MEDIA_IMAGES
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
activity,
Manifest.permission.READ_MEDIA_VIDEO
) == PackageManager.PERMISSION_GRANTED
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
true // Android 10+在作用域存储下不需要权限
} else {
ContextCompat.checkSelfPermission(
activity,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
}
4. 多媒体播放功能实现
4.1 音频播放的完整生命周期管理
使用MediaPlayer时最常见的错误是忘记释放资源。正确的做法是:
kotlin复制class AudioPlayer : MediaPlayer.OnCompletionListener {
private var mediaPlayer: MediaPlayer? = null
fun play(context: Context, @RawRes resId: Int) {
releasePlayer()
mediaPlayer = MediaPlayer.create(context, resId).apply {
setOnCompletionListener(this@AudioPlayer)
start()
}
}
override fun onCompletion(mp: MediaPlayer?) {
releasePlayer()
}
fun releasePlayer() {
mediaPlayer?.run {
if (isPlaying) stop()
release()
}
mediaPlayer = null
}
}
4.2 视频播放的进阶技巧
虽然VideoView使用简单,但在实际项目中我们还需要处理:
- 宽高比适配
- 缓冲优化
- 全屏切换
- 手势控制(进度、音量等)
一个增强版的VideoView实现:
kotlin复制class EnhancedVideoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : VideoView(context, attrs) {
private var originalWidth = 0
private var originalHeight = 0
override fun setVideoURI(uri: Uri?) {
super.setVideoURI(uri)
// 获取视频原始尺寸
setOnPreparedListener { mp ->
originalWidth = mp.videoWidth
originalHeight = mp.videoHeight
requestLayout()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (originalWidth == 0 || originalHeight == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = (width * originalHeight / originalWidth.toFloat()).toInt()
setMeasuredDimension(width, height)
}
}
5. 常见问题排查与性能优化
5.1 多媒体功能常见崩溃场景
- 相机崩溃:
- 未检查设备是否有相机硬件
- 未处理相机被占用的情况
- 未适配不同分辨率的摄像头
解决方案:
kotlin复制fun isCameraAvailable(context: Context): Boolean {
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
fun checkCameraPermission(activity: Activity): Boolean {
return ContextCompat.checkSelfPermission(
activity,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
- 媒体播放崩溃:
- 未处理网络媒体源的超时
- 未考虑设备解码能力
- 内存泄漏
5.2 多媒体性能优化建议
-
图片处理:
- 使用Glide或Coil加载图片
- 对大图进行采样压缩
- 实现图片内存缓存
-
视频优化:
- 预加载关键帧
- 实现分段缓冲
- 根据网络状况调整分辨率
-
音频优化:
- 使用合适的音频格式(AAC优于MP3)
- 实现音频焦点管理
- 处理蓝牙设备连接状态
6. 项目架构设计与扩展建议
6.1 多媒体模块的架构设计
推荐采用分层架构:
- 表现层:Activity/Fragment处理UI交互
- 领域层:封装多媒体核心逻辑
- 数据层:处理文件存储和网络请求
使用依赖注入管理MediaPlayer等重量级对象:
kotlin复制@Module
@InstallIn(SingletonComponent::class)
object MediaModule {
@Provides
@Singleton
fun provideMediaPlayer(): MediaPlayer {
return MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
}
}
}
6.2 扩展功能建议
-
使用CameraX替代传统Camera API:
- 更简单的API
- 更好的设备兼容性
- 内置生命周期管理
-
采用ExoPlayer替代MediaPlayer:
- 支持更多媒体格式
- 更好的扩展性
- Google官方维护
-
实现自定义相机功能:
- 实时滤镜
- 人脸识别
- AR特效
在实际项目中,我发现合理使用这些多媒体功能可以显著提升用户留存率。特别是在社交类应用中,良好的多媒体体验能让用户更愿意分享内容。建议根据具体业务场景选择合适的技术方案,不必一味追求最复杂的技术。