问题描述
目前,我需要一个绑定(音乐)服务,因为我需要与之交互。但我也希望它不会被停止,即使所有组件都已解除绑定。
我的服务代码:
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)
}
}
}
}
}
清单文件声明:
<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()
}
}
}
}
即使在活动被破坏后,我怎样才能让我的服务在后台运行。我参考了 StackOverflow 的几个线程,但它们没有帮助。
解决方法
-
使用 Service 而不是 LifecycleService 作为父类。
-
分别向 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 } }
-
在 mainfest 服务中添加以下标签
android:foregroundServiceType="mediaPlayback"
- 您应该从 Activity 作为前台启动服务
一旦每个客户端解除绑定,绑定服务就会停止,并且 that happens automatically 当客户端(您的 Activity
)被销毁时
如果您的应用程序销毁客户端时您的客户端仍绑定到服务,则销毁会导致客户端解除绑定。最好在与客户端交互完成后立即解除绑定客户端服务。这样做可以关闭空闲服务。
如果您希望服务继续运行,Started Service 可以做到这一点。您仍然可以绑定到它,但它不会停止,除非您明确告诉它停止并且没有绑定客户端。
老实说,如果您正在制作某种媒体播放器,您可能需要使用 MediaBrowserServiceCompat
框架。它允许您创建一个与 MediaBrowser
(它进行绑定等)配合良好的服务,并允许您使用 MediaSession
来获取带有控件的媒体通知等等。
关于这些东西的一些链接:
- MediaBrowserServiceCompat and the modern media playback app 来自 Android 团队的 Ian Lake
- Background Audio in Android With MediaSessionCompat - Java 但陷入了很多你不得不争论的废话
- Developer docs about building media apps - 这里有几个部分,它们都比较分散,我觉得其他链接提供了更好的概述
如果您不关心其中任何一个,那么 startService
/startForegroundService
(或 ContextCompat#startForegroundService
)将为您提供一个刚刚运行的服务,但这些链接可能会给您一些提示关于东西