Service是Android四大组件之一,用于在后台执行长时间运行的操作,无需用户界面。理解Service的工作原理对于开发音乐播放器、文件下载、位置跟踪等应用至关重要。本文将深入探讨Started
Service和Bound Service的区别,并通过完整的音乐播放器示例展示实际应用。
一、Service基础概念与生命周期
1.1 Service是什么?
Service是一种在后台执行长时间运行操作的Android组件,它没有用户界面,可以在应用退出后继续运行。Service主要用于:
- 音乐播放
- 文件下载/上传
- 位置跟踪
- 后台数据同步
- 推送消息处理
1.2 Service与Thread的区别
class ServiceVsThreadActivity : AppCompatActivity() {
fun demonstrateDifferences() {
// 1. Thread(线程)
val thread = Thread {
// 执行后台任务
Thread.sleep(5000)
// 不能直接更新UI,需要Handler
runOnUiThread {
textView.text = "线程任务完成"
}
}
thread.start()
// 2. Service(服务)
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)
// 主要区别:
// - Service是Android组件,生命周期由系统管理
// - Thread是Java线程,生命周期由代码控制
// - Service优先级更高,不易被系统回收
// - Service可以跨进程通信(AIDL)
}
}
1.3 Service生命周期
abstract class BaseService : Service() {
companion object {
const val TAG = "ServiceLifecycle"
}
// 1. 创建Service(只调用一次)
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: Service被创建")
// 初始化资源:播放器、网络连接、数据库等
}
// 2. 启动Service(可多次调用)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: 启动服务, startId=$startId")
// 处理启动请求
return super.onStartCommand(intent, flags, startId)
}
// 3. 绑定Service(可多次绑定)
override fun onBind(intent: Intent?): IBinder? {
Log.d(TAG, "onBind: 绑定服务")
return null // 返回Binder对象
}
// 4. 解除绑定(所有客户端都解绑后调用)
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind: 解除绑定")
return super.onUnbind(intent)
}
// 5. 重新绑定
override fun onRebind(intent: Intent?) {
super.onRebind(intent)
Log.d(TAG, "onRebind: 重新绑定")
}
// 6. 销毁Service(只调用一次)
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: Service被销毁")
// 释放资源:停止播放器、关闭连接等
}
}
1.4 Service生命周期图示
Started Service:
onCreate() → onStartCommand() → 运行中 → stopService() → onDestroy()
Bound Service:
onCreate() → onBind() → 绑定中 → onUnbind() → onDestroy()
混合模式:
onCreate() → onStartCommand() → onBind() → 运行中 → onUnbind() → onDestroy()
二、Started Service详解
2.1 基本Started Service
class BasicStartedService : Service() {
companion object {
const val ACTION_START = "ACTION_START"
const val ACTION_STOP = "ACTION_STOP"
const val EXTRA_DATA = "EXTRA_DATA"
}
private var isRunning = false
private lateinit var notificationManager: NotificationManagerCompat
private val notificationId = 1001
override fun onCreate() {
super.onCreate()
Log.d("StartedService", "Service已创建")
notificationManager = NotificationManagerCompat.from(this)
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("StartedService", "onStartCommand: startId=$startId, flags=$flags")
when (intent?.action) {
ACTION_START -> {
val data = intent.getStringExtra(EXTRA_DATA)
startBackgroundTask(data, startId)
}
ACTION_STOP -> {
stopForeground(true)
stopSelf()
}
else -> {
// 默认启动任务
startBackgroundTask(null, startId)
}
}
// 返回值说明:
// START_STICKY:Service被杀死后会重新创建,但Intent为null
// START_NOT_STICKY:Service被杀死后不会重新创建
// START_REDELIVER_INTENT:Service被杀死后会重新创建,并传递最后的Intent
return START_STICKY
}
private fun startBackgroundTask(data: String?, startId: Int) {
if (isRunning) {
Log.d("StartedService", "任务已在运行中")
return
}
isRunning = true
// 启动前台服务(Android 8.0+必须)
startForeground(notificationId, createNotification("服务运行中"))
// 模拟后台任务
Thread {
try {
for (i in 1..10) {
if (!isRunning) break
Log.d("StartedService", "处理任务: $i, 数据: $data")
// 更新通知
updateNotification("处理中: $i/10")
Thread.sleep(1000)
}
// 任务完成,停止服务
stopForeground(true)
stopSelf(startId)
} catch (e: InterruptedException) {
Log.e("StartedService", "任务被中断", e)
} finally {
isRunning = false
}
}.start()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"started_service_channel",
"Started Service",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Started Service通知通道"
}
notificationManager.createNotificationChannel(channel)
}
}
private fun createNotification(content: String): Notification {
return NotificationCompat.Builder(this, "started_service_channel")
.setContentTitle("Started Service")
.setContentText(content)
.setSmallIcon(R.drawable.ic_notification)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
}
private fun updateNotification(content: String) {
val notification = createNotification(content)
notificationManager.notify(notificationId, notification)
}
override fun onBind(intent: Intent?): IBinder? {
// Started Service通常不绑定
return null
}
override fun onDestroy() {
super.onDestroy()
Log.d("StartedService", "Service已销毁")
isRunning = false
}
}
// 在Activity中启动Service
class StartedServiceActivity : AppCompatActivity() {
fun controlStartedService() {
// 1. 启动Service
val startIntent = Intent(this, BasicStartedService::class.java).apply {
action = BasicStartedService.ACTION_START
putExtra(BasicStartedService.EXTRA_DATA, "任务数据")
}
startService(startIntent)
// 2. 停止Service
val stopIntent = Intent(this, BasicStartedService::class.java).apply {
action = BasicStartedService.ACTION_STOP
}
startService(stopIntent)
// 3. 直接停止Service
stopService(Intent(this, BasicStartedService::class.java))
// 4. 启动前台Service(API 26+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(startIntent)
} else {
startService(startIntent)
}
}
}
2.2 IntentService(已废弃,但需要了解)
// IntentService在API 30中已废弃,但概念重要
// 现在推荐使用WorkManager或JobIntentService
@Deprecated("使用WorkManager代替")
class MyIntentService : IntentService("MyIntentService") {
companion object {
const val ACTION_TASK1 = "ACTION_TASK1"
const val ACTION_TASK2 = "ACTION_TASK2"
}
override fun onCreate() {
super.onCreate()
Log.d("IntentService", "Service创建")
}
override fun onHandleIntent(intent: Intent?) {
// 在后台线程执行
when (intent?.action) {
ACTION_TASK1 -> performTask1()
ACTION_TASK2 -> performTask2()
}
}
private fun performTask1() {
for (i in 1..5) {
Log.d("IntentService", "任务1: $i")
Thread.sleep(1000)
}
}
private fun performTask2() {
for (i in 1..3) {
Log.d("IntentService", "任务2: $i")
Thread.sleep(1500)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("IntentService", "Service销毁")
}
}
三、Bound Service详解
3.1 本地Bound Service(同一进程)
class LocalBoundService : Service() {
inner class LocalBinder : Binder() {
fun getService(): LocalBoundService = this@LocalBoundService
}
private val binder = LocalBinder()
private var counter = 0
override fun onCreate() {
super.onCreate()
Log.d("BoundService", "本地Bound Service已创建")
}
override fun onBind(intent: Intent?): IBinder {
Log.d("BoundService", "绑定本地Service")
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d("BoundService", "解绑本地Service")
return super.onUnbind(intent)
}
// Service提供的方法
fun performAction(data: String): String {
counter++
return "处理数据: $data, 计数: $counter"
}
fun getCounter(): Int = counter
fun resetCounter() {
counter = 0
}
override fun onDestroy() {
super.onDestroy()
Log.d("BoundService", "本地Bound Service已销毁")
}
}
// 在Activity中绑定和使用Service
class BoundServiceActivity : AppCompatActivity() {
private var boundService: LocalBoundService? = null
private var isBound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d("BoundService", "Service已连接")
val binder = service as LocalBoundService.LocalBinder
boundService = binder.getService()
isBound = true
// 使用Service方法
val result = boundService?.performAction("测试数据")
Log.d("BoundService", "结果: $result")
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d("BoundService", "Service连接断开")
isBound = false
boundService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 绑定Service
val intent = Intent(this, LocalBoundService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
fun useBoundService() {
if (isBound) {
// 调用Service方法
val result = boundService?.performAction("用户操作")
textView.text = result
val count = boundService?.getCounter()
Log.d("BoundService", "当前计数: $count")
}
}
fun resetService() {
boundService?.resetCounter()
}
override fun onDestroy() {
super.onDestroy()
// 解除绑定
if (isBound) {
unbindService(connection)
isBound = false
}
}
}
3.2 Messenger Bound Service(跨进程通信)
// Service端
class MessengerService : Service() {
companion object {
const val MSG_REGISTER_CLIENT = 1
const val MSG_UNREGISTER_CLIENT = 2
const val MSG_SET_VALUE = 3
const val MSG_GET_VALUE = 4
}
private var value = 0
private val clients = mutableListOf<Messenger>()
// 处理客户端消息的Handler
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_REGISTER_CLIENT -> {
val client = msg.replyTo
if (!clients.contains(client)) {
clients.add(client)
Log.d("MessengerService", "客户端注册,当前客户端数: ${clients.size}")
}
}
MSG_UNREGISTER_CLIENT -> {
val client = msg.replyTo
clients.remove(client)
Log.d("MessengerService", "客户端注销,当前客户端数: ${clients.size}")
}
MSG_SET_VALUE -> {
value = msg.arg1
Log.d("MessengerService", "设置值: $value")
// 通知所有客户端
notifyClients()
}
MSG_GET_VALUE -> {
val client = msg.replyTo
sendValueToClient(client)
}
else -> super.handleMessage(msg)
}
}
}
private val messenger = Messenger(handler)
override fun onCreate() {
super.onCreate()
Log.d("MessengerService", "Messenger Service已创建")
}
override fun onBind(intent: Intent?): IBinder {
Log.d("MessengerService", "绑定Messenger Service")
return messenger.binder
}
private fun notifyClients() {
val message = Message.obtain(null, MSG_SET_VALUE, value, 0)
val iterator = clients.iterator()
while (iterator.hasNext()) {
val client = iterator.next()
try {
client.send(message)
} catch (e: RemoteException) {
Log.e("MessengerService", "发送消息失败", e)
iterator.remove()
}
}
}
private fun sendValueToClient(client: Messenger) {
val message = Message.obtain(null, MSG_SET_VALUE, value, 0)
try {
client.send(message)
} catch (e: RemoteException) {
Log.e("MessengerService", "发送值失败", e)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("MessengerService", "Messenger Service已销毁")
clients.clear()
}
}
// 客户端Activity
class MessengerClientActivity : AppCompatActivity() {
private var serviceMessenger: Messenger? = null
private var isBound = false
// 客户端Messenger
private val clientMessenger = Messenger(ClientHandler())
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d("MessengerClient", "Service已连接")
serviceMessenger = Messenger(service)
isBound = true
// 注册客户端
registerClient()
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d("MessengerClient", "Service连接断开")
isBound = false
serviceMessenger = null
}
}
inner class ClientHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MessengerService.MSG_SET_VALUE -> {
val value = msg.arg1
Log.d("MessengerClient", "收到新值: $value")
textView.text = "当前值: $value"
}
else -> super.handleMessage(msg)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 绑定Service
val intent = Intent(this, MessengerService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
private fun registerClient() {
val message = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT)
message.replyTo = clientMessenger
try {
serviceMessenger?.send(message)
} catch (e: RemoteException) {
Log.e("MessengerClient", "注册失败", e)
}
}
fun setValue(value: Int) {
if (isBound) {
val message = Message.obtain(null, MessengerService.MSG_SET_VALUE, value, 0)
message.replyTo = clientMessenger
try {
serviceMessenger?.send(message)
} catch (e: RemoteException) {
Log.e("MessengerClient", "设置值失败", e)
}
}
}
fun getValue() {
if (isBound) {
val message = Message.obtain(null, MessengerService.MSG_GET_VALUE)
message.replyTo = clientMessenger
try {
serviceMessenger?.send(message)
} catch (e: RemoteException) {
Log.e("MessengerClient", "获取值失败", e)
}
}
}
override fun onDestroy() {
super.onDestroy()
// 注销客户端
if (isBound && serviceMessenger != null) {
val message = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT)
message.replyTo = clientMessenger
try {
serviceMessenger?.send(message)
} catch (e: RemoteException) {
Log.e("MessengerClient", "注销失败", e)
}
}
// 解除绑定
if (isBound) {
unbindService(connection)
isBound = false
}
}
}
四、前台Service与音乐播放器实战
4.1 音乐播放器Service实现
class MusicPlayerService : Service(), MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
companion object {
const val ACTION_PLAY = "ACTION_PLAY"
const val ACTION_PAUSE = "ACTION_PAUSE"
const val ACTION_STOP = "ACTION_STOP"
const val ACTION_NEXT = "ACTION_NEXT"
const val ACTION_PREVIOUS = "ACTION_PREVIOUS"
const val ACTION_SEEK_TO = "ACTION_SEEK_TO"
const val EXTRA_SONG_URI = "EXTRA_SONG_URI"
const val EXTRA_SEEK_POSITION = "EXTRA_SEEK_POSITION"
const val NOTIFICATION_ID = 1002
const val CHANNEL_ID = "music_player_channel"
}
// 播放状态
enum class PlaybackState {
IDLE, PREPARING, PLAYING, PAUSED, STOPPED, ERROR
}
private lateinit var mediaPlayer: MediaPlayer
private var playbackState = PlaybackState.IDLE
private var currentSongUri: Uri? = null
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var audioManager: AudioManager
private lateinit var mediaSession: MediaSessionCompat
// 音乐列表
private val playlist = mutableListOf<Song>()
private var currentSongIndex = 0
inner class MusicBinder : Binder() {
fun getService(): MusicPlayerService = this@MusicPlayerService
}
private val binder = MusicBinder()
data class Song(
val id: Long,
val title: String,
val artist: String,
val album: String,
val duration: Long,
val uri: Uri
)
override fun onCreate() {
super.onCreate()
Log.d("MusicPlayer", "音乐播放器Service已创建")
// 初始化组件
initMediaPlayer()
initAudioFocus()
initMediaSession()
createNotificationChannel()
notificationManager = NotificationManagerCompat.from(this)
// 加载播放列表(示例)
loadPlaylist()
}
private fun initMediaPlayer() {
mediaPlayer = MediaPlayer().apply {
setOnPreparedListener(this@MusicPlayerService)
setOnCompletionListener(this@MusicPlayerService)
setOnErrorListener(this@MusicPlayerService)
}
}
private fun initAudioFocus() {
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
}
private fun initMediaSession() {
mediaSession = MediaSessionCompat(this, "MusicPlayerService").apply {
setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
play()
}
override fun onPause() {
pause()
}
override fun onStop() {
stop()
}
override fun onSkipToNext() {
playNext()
}
override fun onSkipToPrevious() {
playPrevious()
}
override fun onSeekTo(pos: Long) {
seekTo(pos.toInt())
}
})
// 设置播放操作
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"音乐播放器",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "音乐播放器通知通道"
setSound(null, null) // 静音
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
}
notificationManager.createNotificationChannel(channel)
}
}
private fun loadPlaylist() {
// 这里应该从数据库或ContentProvider加载
// 示例数据
playlist.apply {
add(Song(
1,
"歌曲1",
"艺术家1",
"专辑1",
180000,
Uri.parse("content://media/external/audio/media/1")
))
add(Song(
2,
"歌曲2",
"艺术家2",
"专辑2",
240000,
Uri.parse("content://media/external/audio/media/2")
))
// 添加更多歌曲...
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MusicPlayer", "onStartCommand: ${intent?.action}")
intent?.let {
handleAction(intent)
}
return START_STICKY
}
private fun handleAction(intent: Intent) {
when (intent.action) {
ACTION_PLAY -> {
val songUri = intent.getParcelableExtra<Uri>(EXTRA_SONG_URI)
if (songUri != null) {
playSong(songUri)
} else {
play()
}
}
ACTION_PAUSE -> pause()
ACTION_STOP -> stop()
ACTION_NEXT -> playNext()
ACTION_PREVIOUS -> playPrevious()
ACTION_SEEK_TO -> {
val position = intent.getIntExtra(EXTRA_SEEK_POSITION, 0)
seekTo(position)
}
}
}
override fun onBind(intent: Intent?): IBinder {
Log.d("MusicPlayer", "绑定音乐播放器Service")
return binder
}
fun playSong(songUri: Uri) {
if (playbackState == PlaybackState.PLAYING ||
playbackState == PlaybackState.PREPARING) {
mediaPlayer.stop()
mediaPlayer.reset()
}
try {
currentSongUri = songUri
playbackState = PlaybackState.PREPARING
mediaPlayer.apply {
reset()
setDataSource(applicationContext, songUri)
prepareAsync() // 异步准备
}
// 请求音频焦点
val result = audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 音频焦点已获取
updateNotification("正在播放")
}
} catch (e: Exception) {
Log.e("MusicPlayer", "播放失败", e)
playbackState = PlaybackState.ERROR
}
}
fun play() {
when (playbackState) {
PlaybackState.PAUSED -> {
mediaPlayer.start()
playbackState = PlaybackState.PLAYING
updateNotification("正在播放")
mediaSession.isActive = true
}
PlaybackState.STOPPED -> {
currentSongUri?.let { playSong(it) }
}
else -> {
// 如果没有歌曲在播放,播放第一首
if (playlist.isNotEmpty() && currentSongUri == null) {
playSong(playlist[0].uri)
}
}
}
}
fun pause() {
if (playbackState == PlaybackState.PLAYING) {
mediaPlayer.pause()
playbackState = PlaybackState.PAUSED
updateNotification("已暂停")
}
}
fun stop() {
if (playbackState == PlaybackState.PLAYING ||
playbackState == PlaybackState.PAUSED) {
mediaPlayer.stop()
mediaPlayer.reset()
playbackState = PlaybackState.STOPPED
// 释放音频焦点
audioManager.abandonAudioFocus(audioFocusChangeListener)
// 停止前台服务
stopForeground(true)
mediaSession.isActive = false
}
}
fun playNext() {
if (playlist.isNotEmpty()) {
currentSongIndex = (currentSongIndex + 1) % playlist.size
val nextSong = playlist[currentSongIndex]
playSong(nextSong.uri)
}
}
fun playPrevious() {
if (playlist.isNotEmpty()) {
currentSongIndex = (currentSongIndex - 1 + playlist.size) % playlist.size
val prevSong = playlist[currentSongIndex]
playSong(prevSong.uri)
}
}
fun seekTo(position: Int) {
if (playbackState == PlaybackState.PLAYING ||
playbackState == PlaybackState.PAUSED) {
mediaPlayer.seekTo(position)
}
}
fun getCurrentPosition(): Int {
return if (playbackState == PlaybackState.PLAYING ||
playbackState == PlaybackState.PAUSED) {
mediaPlayer.currentPosition
} else {
0
}
}
fun getDuration(): Int {
return if (playbackState == PlaybackState.PLAYING ||
playbackState == PlaybackState.PAUSED) {
mediaPlayer.duration
} else {
0
}
}
fun isPlaying(): Boolean {
return playbackState == PlaybackState.PLAYING
}
fun getCurrentSong(): Song? {
return if (currentSongIndex in playlist.indices) {
playlist[currentSongIndex]
} else {
null
}
}
fun getPlaylist(): List<Song> = playlist
fun addToPlaylist(song: Song) {
playlist.add(song)
}
fun removeFromPlaylist(songId: Long) {
playlist.removeAll { it.id == songId }
}
// MediaPlayer回调
override fun onPrepared(mp: MediaPlayer?) {
Log.d("MusicPlayer", "媒体准备完成")
playbackState = PlaybackState.PLAYING
mediaPlayer.start()
// 启动前台服务
startForeground(NOTIFICATION_ID, createMusicNotification())
// 发送广播通知播放开始
sendBroadcast(Intent("com.example.MUSIC_PLAYING"))
// 更新媒体会话
updateMediaSession()
}
override fun onCompletion(mp: MediaPlayer?) {
Log.d("MusicPlayer", "播放完成")
playbackState = PlaybackState.STOPPED
// 自动播放下一首
playNext()
// 发送广播通知播放完成
sendBroadcast(Intent("com.example.MUSIC_COMPLETED"))
}
override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
Log.e("MusicPlayer", "播放错误: what=$what, extra=$extra")
playbackState = PlaybackState.ERROR
// 发送广播通知错误
val intent = Intent("com.example.MUSIC_ERROR").apply {
putExtra("what", what)
putExtra("extra", extra)
}
sendBroadcast(intent)
return true // 错误已处理
}
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
// 重新获取焦点,恢复播放
if (playbackState == PlaybackState.PAUSED) {
play()
}
}
AudioManager.AUDIOFOCUS_LOSS -> {
// 永久失去焦点,停止播放
pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时失去焦点,暂停播放
if (isPlaying()) {
pause()
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// 短暂失去焦点,降低音量
mediaPlayer.setVolume(0.2f, 0.2f)
}
}
}
private fun createMusicNotification(): Notification {
val currentSong = getCurrentSong()
// 创建播放控制PendingIntent
val playIntent = PendingIntent.getService(
this,
0,
Intent(this, MusicPlayerService::class.java).apply {
action = ACTION_PLAY
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val pauseIntent = PendingIntent.getService(
this,
1,
Intent(this, MusicPlayerService::class.java).apply {
action = ACTION_PAUSE
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val stopIntent = PendingIntent.getService(
this,
2,
Intent(this, MusicPlayerService::class.java).apply {
action = ACTION_STOP
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val nextIntent = PendingIntent.getService(
this,
3,
Intent(this, MusicPlayerService::class.java).apply {
action = ACTION_NEXT
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val prevIntent = PendingIntent.getService(
this,
4,
Intent(this, MusicPlayerService::class.java).apply {
action = ACTION_PREVIOUS
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 创建通知
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(currentSong?.title ?: "音乐播放器")
.setContentText(currentSong?.artist ?: "未知艺术家")
.setSmallIcon(R.drawable.ic_music_note)
.setLargeIcon(getAlbumArt(currentSong))
.setStyle(androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(0, 1, 2))
.addAction(
NotificationCompat.Action(
R.drawable.ic_skip_previous,
"上一首",
prevIntent
)
)
.addAction(
if (isPlaying()) {
NotificationCompat.Action(
R.drawable.ic_pause,
"暂停",
pauseIntent
)
} else {
NotificationCompat.Action(
R.drawable.ic_play,
"播放",
playIntent
)
}
)
.addAction(
NotificationCompat.Action(
R.drawable.ic_skip_next,
"下一首",
nextIntent
)
)
.addAction(
NotificationCompat.Action(
R.drawable.ic_stop,
"停止",
stopIntent
)
)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOngoing(true)
.build()
}
private fun updateNotification(status: String) {
val notification = createMusicNotification()
notificationManager.notify(NOTIFICATION_ID, notification)
}
private fun updateMediaSession() {
val currentSong = getCurrentSong()
val metadata = MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentSong?.title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentSong?.artist)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentSong?.album)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, currentSong?.duration ?: 0)
.build()
mediaSession.setMetadata(metadata)
}
private fun getAlbumArt(song: Song?): Bitmap? {
// 这里应该从MediaStore获取专辑封面
// 示例:返回默认图片
return null
}
override fun onDestroy() {
super.onDestroy()
Log.d("MusicPlayer", "音乐播放器Service已销毁")
// 释放资源
mediaPlayer.release()
mediaSession.release()
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
}
4.2 音乐播放器Activity
class MusicPlayerActivity : AppCompatActivity() {
private var musicService: MusicPlayerService? = null
private var isBound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d("MusicPlayerUI", "音乐服务已连接")
val binder = service as MusicPlayerService.MusicBinder
musicService = binder.getService()
isBound = true
updateUI()
setupSeekBar()
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d("MusicPlayerUI", "音乐服务连接断开")
isBound = false
musicService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_music_player)
// 绑定音乐服务
val intent = Intent(this, MusicPlayerService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
// 启动服务(确保在后台运行)
startService(intent)
setupUI()
}
private fun setupUI() {
// 播放/暂停按钮
btnPlayPause.setOnClickListener {
if (musicService?.isPlaying() == true) {
pauseMusic()
} else {
playMusic()
}
}
// 停止按钮
btnStop.setOnClickListener {
stopMusic()
}
// 下一首按钮
btnNext.setOnClickListener {
playNext()
}
// 上一首按钮
btnPrevious.setOnClickListener {
playPrevious()
}
// 播放列表按钮
btnPlaylist.setOnClickListener {
showPlaylist()
}
}
private fun setupSeekBar() {
// 更新进度
val handler = Handler(Looper.getMainLooper())
val updateSeekBar = object : Runnable {
override fun run() {
if (isBound && musicService?.isPlaying() == true) {
val currentPosition = musicService?.getCurrentPosition() ?: 0
val duration = musicService?.getDuration() ?: 1
seekBar.progress = (currentPosition.toFloat() / duration * 100).toInt()
txtProgress.text = formatTime(currentPosition)
txtDuration.text = formatTime(duration)
}
handler.postDelayed(this, 1000)
}
}
handler.post(updateSeekBar)
// 拖动SeekBar
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser && isBound) {
val duration = musicService?.getDuration() ?: 0
val newPosition = (progress.toFloat() / 100 * duration).toInt()
txtProgress.text = formatTime(newPosition)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
// 开始拖动
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (isBound) {
val duration = musicService?.getDuration() ?: 0
val newPosition = (seekBar?.progress?.toFloat() ?: 0f) / 100 * duration
musicService?.seekTo(newPosition.toInt())
}
}
})
}
private fun playMusic() {
if (isBound) {
musicService?.play()
updateUI()
}
}
private fun pauseMusic() {
if (isBound) {
musicService?.pause()
updateUI()
}
}
private fun stopMusic() {
if (isBound) {
musicService?.stop()
updateUI()
}
}
private fun playNext() {
if (isBound) {
musicService?.playNext()
updateUI()
}
}
private fun playPrevious() {
if (isBound) {
musicService?.playPrevious()
updateUI()
}
}
private fun updateUI() {
if (isBound) {
val isPlaying = musicService?.isPlaying() == true
val currentSong = musicService?.getCurrentSong()
// 更新按钮
btnPlayPause.text = if (isPlaying) "暂停" else "播放"
btnPlayPause.setCompoundDrawablesWithIntrinsicBounds(
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
0, 0, 0
)
// 更新歌曲信息
currentSong?.let { song ->
txtTitle.text = song.title
txtArtist.text = song.artist
txtAlbum.text = song.album
}
}
}
private fun showPlaylist() {
if (isBound) {
val playlist = musicService?.getPlaylist() ?: emptyList()
val adapter = ArrayAdapter(
this,
android.R.layout.simple_list_item_1,
playlist.map { "${it.title} - ${it.artist}" }
)
val dialog = AlertDialog.Builder(this)
.setTitle("播放列表")
.setAdapter(adapter) { _, position ->
val song = playlist[position]
musicService?.playSong(song.uri)
updateUI()
}
.setNegativeButton("关闭", null)
.create()
dialog.show()
}
}
private fun formatTime(milliseconds: Int): String {
val totalSeconds = milliseconds / 1000
val minutes = totalSeconds / 60
val seconds = totalSeconds % 60
return String.format("%02d:%02d", minutes, seconds)
}
override fun onDestroy() {
super.onDestroy()
// 解除绑定(但Service可能仍在后台运行)
if (isBound) {
unbindService(connection)
isBound = false
}
}
}
五、Service最佳实践与兼容性
5.1 JobIntentService(替代IntentService)
class MyJobIntentService : JobIntentService() {
companion object {
const val JOB_ID = 1000
fun enqueueWork(context: Context, intent: Intent) {
enqueueWork(
context,
MyJobIntentService::class.java,
JOB_ID,
intent
)
}
}
override fun onHandleWork(intent: Intent) {
// 在后台线程执行
val action = intent.action ?: return
when (action) {
"ACTION_UPLOAD" -> uploadFile(intent)
"ACTION_DOWNLOAD" -> downloadFile(intent)
"ACTION_PROCESS" -> processData(intent)
}
}
private fun uploadFile(intent: Intent) {
val filePath = intent.getStringExtra("file_path")
Log.d("JobIntentService", "开始上传文件: $filePath")
// 模拟上传
for (i in 1..10) {
if (isStopped) return // 检查是否被停止
Log.d("JobIntentService", "上传进度: ${i * 10}%")
Thread.sleep(500)
}
Log.d("JobIntentService", "文件上传完成")
// 发送广播通知完成
val broadcast = Intent("com.example.UPLOAD_COMPLETE")
sendBroadcast(broadcast)
}
private fun downloadFile(intent: Intent) {
val url = intent.getStringExtra("url")
Log.d("JobIntentService", "开始下载: $url")
// 模拟下载
Thread.sleep(3000)
Log.d("JobIntentService", "下载完成")
}
private fun processData(intent: Intent) {
val data = intent.getStringExtra("data")
Log.d("JobIntentService", "处理数据: $data")
// 模拟数据处理
Thread.sleep(2000)
Log.d("JobIntentService", "数据处理完成")
}
override fun onDestroy() {
super.onDestroy()
Log.d("JobIntentService", "Service销毁")
}
}
5.2 WorkManager(现代后台任务解决方案)
class BackgroundWorkManager {
fun scheduleWork() {
// 1. 创建约束条件
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.build()
// 2. 创建WorkRequest
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10,
TimeUnit.SECONDS
)
.setInputData(
workDataOf(
"file_path" to "/path/to/file.txt",
"description" to "重要文件"
)
)
.build()
// 3. 调度工作
WorkManager.getInstance(context).enqueue(uploadWork)
// 4. 观察工作状态
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadWork.id)
.observe(lifecycleOwner) { workInfo ->
when (workInfo?.state) {
WorkInfo.State.ENQUEUED -> Log.d("WorkManager", "任务已排队")
WorkInfo.State.RUNNING -> Log.d("WorkManager", "任务运行中")
WorkInfo.State.SUCCEEDED -> Log.d("WorkManager", "任务成功")
WorkInfo.State.FAILED -> Log.d("WorkManager", "任务失败")
WorkInfo.State.BLOCKED -> Log.d("WorkManager", "任务被阻塞")
WorkInfo.State.CANCELLED -> Log.d("WorkManager", "任务被取消")
null -> Unit
}
}
}
}
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
val filePath = inputData.getString("file_path")
val description = inputData.getString("description")
Log.d("UploadWorker", "开始上传: $description")
// 模拟上传
for (i in 1..10) {
if (isStopped) {
return Result.failure()
}
// 更新进度
setProgress(workDataOf("progress" to i * 10))
Thread.sleep(500)
}
Log.d("UploadWorker", "上传完成")
Result.success()
} catch (e: Exception) {
Log.e("UploadWorker", "上传失败", e)
Result.failure()
}
}
}
5.3 Service兼容性处理
class CompatibleService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 检查是否需要前台服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ 必须使用前台服务
startForegroundServiceIfNeeded()
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+ 的前台服务限制
checkForegroundServicePermission()
}
return super.onStartCommand(intent, flags, startId)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun startForegroundServiceIfNeeded() {
val notification = createNotification()
startForeground(NOTIFICATION_ID, notification)
}
@RequiresApi(Build.VERSION_CODES.S)
private fun checkForegroundServicePermission() {
if (checkSelfPermission(Manifest.permission.FOREGROUND_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
Log.w("CompatibleService", "缺少前台服务权限")
}
}
private fun createNotification(): Notification {
val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
"service_channel"
} else {
""
}
return NotificationCompat.Builder(this, channelId)
.setContentTitle("兼容性服务")
.setContentText("在不同版本Android上运行")
.setSmallIcon(R.drawable.ic_notification)
.setPriority(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.PRIORITY_LOW
} else {
NotificationCompat.PRIORITY_DEFAULT
}
)
.build()
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val channel = NotificationChannel(
"service_channel",
"服务通道",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
六、面试常见问题
6.1 Service基础概念
-
Service是什么?它和Thread有什么区别?
- Service是Android组件,生命周期由系统管理;Thread是Java线程,由代码控制
- Service优先级更高,不易被系统回收
- Service可以跨进程通信
-
Service的生命周期方法有哪些?
- onCreate(), onStartCommand(), onBind(), onUnbind(), onDestroy()
-
Started Service和Bound Service有什么区别?
- Started Service通过startService()启动,独立运行
- Bound Service通过bindService()绑定,与客户端生命周期关联
- 可以同时使用两种方式(混合模式)
6.2 Service启动与绑定
-
如何启动和停止Started Service?
- startService(intent)启动,stopService(intent)或stopSelf()停止
-
如何绑定和解除绑定Bound Service?
- bindService(intent, connection, flags)绑定
- unbindService(connection)解除绑定
-
ServiceConnection接口有哪些方法?
- onServiceConnected():Service连接成功时调用
- onServiceDisconnected():Service异常断开时调用
6.3 Service通信方式
-
Bound Service有哪些通信方式?
- Binder(本地Service)
- Messenger(跨进程,基于AIDL)
- AIDL(高级跨进程通信)
-
如何使用Messenger进行Service通信?
- Service端创建Handler和Messenger
- 客户端通过Messenger发送Message
- 需要双向通信时,客户端也需要创建Messenger
6.4 前台Service
-
什么是前台Service?为什么需要它?
- 前台Service有可见的通知,优先级更高
- Android 8.0+要求后台Service必须转为前台Service
- 用于音乐播放、位置跟踪等需要持续运行的任务
-
如何创建前台Service?
- 创建Notification并调用startForeground(notificationId, notification)
6.5 音乐播放器实现
-
实现音乐播放器时需要注意什么?
- 音频焦点管理(AudioFocus)
- 媒体按钮处理(MediaSession)
- 播放状态同步(UI与Service)
- 通知栏控制
-
如何处理音频焦点?
- 使用AudioManager.requestAudioFocus()
- 实现OnAudioFocusChangeListener处理焦点变化
- 播放前请求焦点,播放后释放焦点
6.6 兼容性与最佳实践
-
Android 8.0对Service有哪些限制?
- 后台Service限制,需要转为前台Service
- 使用startForegroundService()启动前台服务
-
IntentService被废弃后,推荐使用什么?
- JobIntentService(兼容版本)
- WorkManager(现代解决方案)
- 协程+Kotlin
-
WorkManager和Service有什么区别?
- WorkManager用于可延迟的后台任务
- Service用于立即执行的长时间任务
- WorkManager自动处理设备重启、电池优化等
6.7 性能与优化
-
如何避免Service内存泄漏?
- 及时解除绑定(unbindService)
- 使用WeakReference引用Context
- 在onDestroy()中释放资源
-
Service在后台被杀死怎么办?
- 使用START_STICKY或START_REDELIVER_INTENT
- 保存状态,重启后恢复
- 使用前台Service提高优先级
6.8 实际应用场景
-
什么时候使用Started Service?什么时候使用Bound Service?
- Started Service:文件下载、音乐播放(需要独立运行)
- Bound Service:需要与Activity交互的任务
- 混合模式:音乐播放器(既独立运行又与UI交互)
-
如何实现跨进程的Service通信?
- 使用Messenger(简单场景)
- 使用AIDL(复杂场景)
- 在Manifest中设置android:process属性
-
Service和BroadcastReceiver如何配合使用?
- Service执行任务,完成后发送广播
- Activity注册广播接收器,接收Service通知
- 实现组件间解耦通信
Service是Android开发中非常重要的组件,掌握其工作原理对于开发高质量应用至关重要。

1119

被折叠的 条评论
为什么被折叠?



