Android-Start方式和Bind方式混合开启Service
Android-Start方式和Bind方式混合开启Service
需求如下
需要开发一个音乐APP,需要满足以下的需求:
- 当退出所有的Activity后仍然能够播放音乐
- 能够控制音乐的播放比如说,暂停,上一首,下一首,获取正在播放的音乐的信息等。
首先所有的Activity都退出后仍然要能够播放音乐,从这一点来看,我们肯定是需要一个服务的并且这个服务还得是通过Start的方式开启的(因为播放音乐需要长时间运行,Bind的方式显然不符合我们的需求)。但是因为我们有需要控制音乐的播放,这时候Bind方式也是不可或缺的(Start 方式没法与Service交互)。
现在,Start和Bind的方式都不能完全地满足我们的需求,那怎么办呢,当然是让他们两在一起了 😂。
建立服务
我们要为播放音乐建立一个MusicService
/**
* 用于音乐播放的服务
* */
class MusicService : Service() {
//与Service想关联的Binder
private val mBinder = MusicBinder()
//音乐列表
private var singList: List<String>? = null
//音乐的索引
private var index = -1
//表示音乐播放是否暂停
private var paused = false
//表示服务是否处于运行状态
private var isRunning = false
//播放事件的标记,每首歌为10秒
var playingCountDown = 0
override fun onBind(intent: Intent?): IBinder {
return this.mBinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//开启线程播放音乐
thread {
while (isRunning) {
if (paused) {
"Music playing has been paused!".logE()
} else {
"${this.singList!![index]} are playing".logE()
playingCountDown++
//每十秒切换下一首音乐
if (playingCountDown >= 10) {
playingCountDown = 0
index++
if (index >= this.singList!!.size)
index = 0
}
}
//线程睡眠一秒
SystemClock.sleep(1000)
}
}
return super.onStartCommand(intent, flags, startId)
}
//当服务创建的时候初始化一些数据
override fun onCreate() {
super.onCreate()
this.singList = listOf("My heart will go on", "清明上河图", "夜的钢琴曲 五", "彩云追月")
this.index = 0
this.isRunning = true
}
//当服务销毁的时候回收资源
override fun onDestroy() {
super.onDestroy()
this.isRunning = false
this.singList = null
}
inner class MusicBinder : Binder() {
fun pause() {
this@MusicService.paused = true
}
fun play() {
this@MusicService.paused = false
}
fun next() {
this@MusicService.index++
if (this@MusicService.index >= this@MusicService.singList?.size!!) {
this@MusicService.index = 0
}
this@MusicService.playingCountDown = 0
}
fun previous() {
this@MusicService.index--
if (this@MusicService.index < 0)
index = this@MusicService.singList!!.size - 1
this@MusicService.playingCountDown = 0
}
fun getSingInfo(): String {
return "Current sing is ${singList!![index]}"
}
}
}
上面的代码虽然有点长,但是都比较简单,其中使用打Log的方式模拟了播放音乐,通过Binder暴露出来了一系列的方法,来控制音乐的播放,在onStartCommand方法中我们开启了一个线程来播放音乐,之所以是要开启线程,是因为播放音乐是一个持续性的工作,不能够在主线程运行。
Activity的代码
首先是布局文件,布局文件没有什么特殊的,仅仅是几个Button来控制音乐的播放
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context="top.littledavid.musicservice.MainActivity">
<Button
android:id="@+id/prepareBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Prepare"
android:textAllCaps="false" />
<Button
android:id="@+id/pauseBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pause"
android:textAllCaps="false" />
<Button
android:id="@+id/nextBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Next"
android:textAllCaps="false" />
<Button
android:id="@+id/previousBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Previous"
android:textAllCaps="false" />
<Button
android:id="@+id/getInfoBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Info"
android:textAllCaps="false" />
</LinearLayout>
Activity的代码
class MainActivity : AppCompatActivity(), View.OnClickListener {
var mBinder: MusicService.MusicBinder? = null
var serviceConn = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mBinder = service as MusicService.MusicBinder
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//注册点击事件
val btnList = listOf<Int>
(R.id.prepareBtn, R.id.pauseBtn, R.id.previousBtn, R.id.nextBtn, R.id.getInfoBtn)
btnList.forEach {
this.findViewById<Button>(it).setOnClickListener(this)
}
}
//各个按钮的点击事件
override fun onClick(v: View?) {
when (v!!.id) {
R.id.prepareBtn -> {//准备播放
//通过混合开启的方式开启服务
val serviceIntent = Intent(this, MusicService::class.java)
this.startService(serviceIntent)
this.bindService(serviceIntent, this.serviceConn, Context.BIND_AUTO_CREATE)
}
R.id.pauseBtn -> { //开始 or 暂停播放
//开始播放
if (pauseBtn.text == "Start") {
mBinder!!.play()
pauseBtn.text = "Pause"
} else {
mBinder!!.pause()
pauseBtn.text = "Start"
}
}
R.id.previousBtn -> { //上一首
mBinder!!.previous()
}
R.id.nextBtn -> { //下一首
mBinder!!.next()
}
R.id.getInfoBtn -> {
mBinder!!.getSingInfo() show this
}
}
}
//回收mBinder
override fun onDestroy() {
super.onDestroy()
this.unbindService(serviceConn)
this.mBinder = null
}
}
上面就时Activity的代码,主要就是通过一系列的按钮来控制音乐的播放。对了不要忘记在 Manifest
文件中配置我们的服务。
首先是建立的匿名的内部类来获取Binder,这个不用过多解释。然后在onCreate方法为一系列的按钮注册了事件。
在 Prepare Button的点击事件中,我们通过start和bind的方式同时开启了Service。或许你会担心,服务会被创建两次,请不必担心,因为在start方式开启service的时候Service已经被创建,所以在通过Bind方式开启服务的时候服务不会被再次创建,仅仅获取了Binder。
在获取了Binder以后,Activity和Service之间就建立了紧密的链接,我们可以通过Binder来控制Service的行为(音乐播放)。
因为我们是混合开启了服务,所以当我们退出Activity以后Log依然在不停的打印,这里因为在onDestroy方法中仅仅是解除了Activity与Service的关联,并不能将Service关闭。这样就实现了我们的需求,可以控制音乐的播放,同时也可以在退出Activity的时候仍然可以继续播放。
如何关闭混合方式开启的服务
在上面的音乐播放服务的Demo中,我们在Activity的 onDestroy
的生命周期方法中调用了 unbindService
方法,但是服务并没有关闭,那么混合方式开启的服务该怎么关闭呢?当然是混合开启,混合关闭了 😥-_-!!.
我们再在Activity中添加一个Button来事件我们关闭服务的业务逻辑:
this.stopService(Intent(this, MusicService::class.java))
this.unbindService(serviceConn)
mBinder = null
this.isShutDown = true
源码下载
源码已经上传至Github 下载地址如下:
https://github.com/littledavid-tech/MusicService