使用start前台服务在Activity销毁后不保留



目前,我需要一个bound (Music)Service,因为我需要与它交互。但我也希望它不会停止,即使所有组件都解除了绑定。

我的服务代码:

class ServicePlayer : LifecycleService() {

var mediaPlayer: MediaPlayer? = null
var notificationManager: NotificationManager? = null
var notificationBuilder: NotificationCompat.Builder? = null
private val mBinder: IBinder = PlayerBinder()
private val NOTIFICATION_ID = 1111


override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_REDELIVER_INTENT
}

inner class PlayerBinder : Binder() {
val service: ServicePlayer
get() = this@ServicePlayer
}

override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mBinder
}

override fun onCreate() {
super.onCreate()

mediaPlayer = MediaPlayer()
mediaPlayer!!.setOnCompletionListener(this)
mediaPlayer!!.setOnBufferingUpdateListener(this)
mediaPlayer!!.setOnErrorListener(this)
val filter = IntentFilter()
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
filter.addAction(Intent.ACTION_SCREEN_ON)
registerReceiver(receiver, filter)
}


override fun onDestroy() {

super.onDestroy()
mediaPlayer!!.reset()
mediaPlayer!!.release()
Log.i("DESTROY SERVICE", "destroy")
unregisterReceiver(receiver)
}



fun play(trackIndex: Int, tracks: ArrayList<Track>?) {

...

val intent = Intent(BUFFERING)
this@ServicePlayer.sendBroadcast(intent)

}

fun pause() {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
PlayerLiveData.isPlaying.value = false
val intent = Intent(UPDATE_UI)
this@ServicePlayer.sendBroadcast(intent)

//Show notification
CoroutineScope(Dispatchers.Default).launch {
showNotification()
}
}
}


private fun hideNotification() {
notificationManager!!.cancel(NOTIFICATION_ID)
stopForeground(true)
}


private fun showNotification() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
...

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val CHANNEL_ID = "controls_channel_id"
val CHANNEL_NAME = "Play tracks"
val channel = NotificationChannel(CHANNEL_ID,CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)
...

val mMediaSession = MediaSessionCompat(applicationContext, getString(R.string.app_name))
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
notificationManager!!.createNotificationChannel(channel)
notificationBuilder = NotificationCompat.Builder(applicationContext)
.setChannelId(CHANNEL_ID)
.setContentText(artistText)
.setContentTitle(track.title)

...
} else {
notificationBuilder = NotificationCompat.Builder(applicationContext)
...
notificationBuilder!!
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentIntent)
.setCustomContentView(remoteSmallViews)
.setCustomBigContentView(remoteViews)
}


CoroutineScope(Dispatchers.Default).launch {
val notification = notificationBuilder!!.build()
startForeground(NOTIFICATION_ID, notification)
val notificationTarget = NotificationTarget(
applicationContext
, R.id.imgThumb, remoteViews
, notification, NOTIFICATION_ID
)
...
lifecycleScope.launch {
val request = ImageRequest.Builder(applicationContext)
.data(thumb)
.error(R.drawable.placeholder_song)
.placeholder(R.drawable.placeholder_song)
.build()
val drawable = imageLoader.execute(request).drawable
val bitmap = (drawable as BitmapDrawable).bitmap


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {


notificationBuilder!!.setLargeIcon(bitmap)
val notification = notificationBuilder!!.build()
notificationManager!!.notify(NOTIFICATION_ID,notification)


//Start Foreground service
startForeground(NOTIFICATION_ID, notification)


} 
}
}

}      
}

Manifest文件声明:

<service android:name=".services.ServicePlayer" android:enabled="true" android:exported="true"/>

在活动中使用服务

class MainActivity : AppCompatActivity() {
lateinit var binding: MainActivityBinding
private lateinit var audioPlayerService: ServicePlayer

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, ServicePlayer::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.lifecycleOwner = this
binding.viewmodel = mainViewModel
}


private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
// audioPlayerService = null;
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
audioPlayerService = (service as ServicePlayer.PlayerBinder).service
if (audioPlayerService.trackIndex !== -1) {
//updatePlaybackUI()
}
}
}

}

如何在activity被销毁后保持服务在后台运行?我参考了几个线程的StackOverflow,但他们没有帮助。

  1. 使用Service代替LifecycleService作为父类

  2. 分别增加onCreate和onDestroy方法的部分唤醒锁启动和停止调用。

    private val powerManager
    get() = (this.getSystemService(Context.POWER_SERVICE) as PowerManager)
    private var wakeLock: PowerManager.WakeLock? = null
    private fun startWakeLock() {
    if (wakeLock == null) {
    wakeLock = powerManager.newWakeLock(
    PowerManager.PARTIAL_WAKE_LOCK,
    "${packageName}:wakeLock"
    )
    wakeLock?.acquire()
    }
    }
    private fun stopWakeLock() {
    if (wakeLock?.isHeld == true) {
    wakeLock?.release()
    wakeLock = null
    }
    }
    
  3. 在mainfest上添加以下标签

android: foregroundServiceType ="mediaPlayback"

  1. 您应该从活动
  2. 启动前台服务

一旦每个客户端解除绑定,绑定的服务就会停止,并且当客户端(您的Activity)被销毁时,会自动发生这种情况

如果你的客户端在你的应用程序销毁客户端时仍然绑定到一个服务,销毁会导致客户端解除绑定。最好的做法是在客户端完成与服务的交互后立即解除绑定。

如果你想让服务继续运行,一个启动的服务将会这样做。你仍然可以绑定到它,但它不会停止,直到你显式地告诉它停止没有客户端绑定。


老实说,如果你正在制作某种媒体播放器,你可能会想要使用MediaBrowserServiceCompat框架。它允许你创建一个服务,与MediaBrowser玩得很好(它做绑定,以及其他事情),并允许你使用MediaSession来获得一个带有控件的媒体通知和所有这些。

关于这些东西的一些链接:

  • MediaBrowserServiceCompat和来自Android团队的Ian Lake的现代媒体播放应用
  • 背景音频在Android与MediaSessionCompat - Java,但进入了很多废话,你将不得不争吵
  • 关于构建媒体应用程序的开发者文档-这里有几个部分,它们都有点分散,我觉得其他链接提供了更好的概述

如果你不关心这些,那么startService/startForegroundService(或ContextCompat#startForegroundService)会给你一个服务,只是运行,但这些链接可能会给你一些关于东西的指针

最新更新