前言
服务是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。 服务不提供用户界面。 例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。诸如 Activity 等其他组件可以启动服务,让其运行或与其绑定以便与其进行交互。虽然现在官方都是强烈推荐使用WorkManager API 替换Service,但是我个人觉得还是有必要好好学习一下的,弄清楚不迷糊。
Service 的类型
前台服务
前台服务会执行一些用户可察觉到的操作。例如,音频应用将使用前台服务来播放音轨。前台服务必须显示 Notification。即使用户没有与应用互动,前台服务也会继续运行。除非服务停止或从前台移除,否则无法关闭此通知。
注意 :WorkManager API 提供了一种灵活的任务调度方法,并能够在需要时将这些作业作为前台服务运行。在许多情况下,使用 WorkManager 比直接使用前台服务更可取。
后台服务
后台服务执行用户不会直接注意到的操作。例如,如果应用使用服务来压缩其存储空间,则该服务通常是后台服务。
注意 :如果您的应用以 API 级别 26 或更高级别为目标平台,那么当应用本身不在前台运行时,系统会对运行后台服务施加限制。例如,在大多数情况下,您不应在后台访问位置信息。请改为使用 WorkManager 调度任务。
绑定服务
当应用组件通过调用 bindService()
绑定到服务时,服务即处于绑定状态。绑定服务提供了一个客户端-服务器接口,该接口允许组件与服务进行交互、发送请求、接收结果,甚至使用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
Service基础知识
常用回调
以下是一些需要重写的重要回调方法:
-
onStartCommand()
当另一个组件(如 activity)请求启动服务时,系统会通过调用
startService()
来调用此方法。执行此方法时,服务会启动并且可以在后台无限期运行。如果您实现此方法,则需负责在服务工作完成后通过调用stopSelf()
或stopService()
来停止服务。如果您只想提供绑定,则无需实现此方法。 -
onBind()
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用
bindService()
来调用此方法。 在此方法的实现中,您必须通过返回IBinder
提供一个接口,以供客户端用来与服务通信。您必须始终实现此方法;但是,如果您不想允许绑定,则应返回 null。 -
onCreate()
首次创建服务时(在调用
onStartCommand()
或onBind()
之前),系统会调用此方法来执行一次性设置过程。如果服务已在运行,则不会调用此方法。 -
onDestroy()
当服务不再使用并且即将被销毁时,系统会调用此方法。您的服务应实现此机制以清理所有资源,例如线程、注册的监听器或接收器。这是服务接收的最后一个调用。
清单文件声明
Service与Activity一样如果要使用的话必须在AndroidMainifest.xml声明,如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
......
>
<service android:name=".MyServices"/>
......
</application>
</manifest>
android:name
属性是唯一的必需属性,用于指定服务的类名称。发布应用后,请勿更改此名称,以免因依赖显式 intent 来启动或绑定服务而破坏代码
注意:
为确保您的应用安全无虞,请务必在启动
Service
时使用显式 intent,并且不要为您的服务声明 intent 过滤器。使用隐式 intent 启动服务存在安全隐患,因为您无法确定响应 intent 的服务,而且用户看不到启动的是哪项服务。从 Android 5.0(API 级别 21)开始,如果使用隐式 intent 调用bindService()
,系统会抛出异常。
Service生命周期
上图描述了Service的生命周期,左边描述了startService
创建的生命周期,右边描述了bindService
创建的生命周期。
Service的整个声明周期介于介于onCreate()
和 onDestroy()
之间。Service的"有效生命周期"从调用 onStartCommand()
或 onBind()
开始。系统会将每个方法传递给 startService()
或 bindService()
的 Intent
。如果是startService
启动的服务,有效生命周期与整个生命周期同时结束,如果是bindService
创建的则有效生命周期在 onUnbind()
返回时结束。
对于bindService
创建的服务,当所有绑定解除的时候会自动销毁(除非是startService启动的),所以如果知识bindService
启动的服务不需要管理其生命周期。
此外,如果您的服务已启动并接受绑定 ,那么当系统调用您的 onUnbind()
方法时,如果您想在客户端下次绑定到服务时收到对 onRebind()
的调用,可以选择返回 true
。onRebind()
返回空值,但客户端仍会在其 onServiceConnected()
回调中接收 IBinder
。下图说明了这种生命周期的逻辑。
后台服务
后台服务是另一个组件通过startService()
启动的服务,这回导致其调用onStartCommand
方法。服务启动之后生命周期独立于启动它的组件,就算启动它的组件销毁了也不影响。可以通过调用 stopSelf()
自行停止运行,或者由其他组件通过调用 stopService()
来停止。组件可以通过startService()
传递数据,服务会在onStartCommand
中接收intent
。
Android 框架还提供了 Service
的 IntentService
子类,该类使用工作器线程逐一处理所有启动请求。不建议 新应用使用此类,因为它从 Android 8 Oreo 开始将无法正常工作,原因是引入了后台执行限制。此外,从 Android 11 开始,它将被废弃。您可以使用 JobIntentService(最新版本也即将废弃推荐使用WorkManager) 替代与更高版本的 Android 兼容的 IntentService
。 通过前面的介绍,相信我们已经对Service有了一个基本的了解,接下来通过实例,一步一步创建一个基础的后台服务。
1.创建一个Service类
kotlin
class MyServices : Service() {
companion object {
const val TAG = "MyServices"
}
override fun onCreate() {
Log.i(TAG, "onCreate")
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
Log.i(TAG, "onDestroy")
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
上面我们创建了一个Service类并复写了前面介绍的几个常用回调,为了方便调试我们还添加了几行日志。
2.在AndroidMainifest.xml申明
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
......
>
<service android:name=".MyServices"/>
......
</application>
</manifest>
3.创建操作布局按钮
新建一个Activity#xml文件,内容如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>
4.服务启动与销毁
这里我们定义了两个按钮一个用于启动Service,一个用于停止Service
kotlin
startService.setOnClickListener {
//启动Service
val intent = Intent(this@MainActivity, MyServices::class.java)
startService(intent)
}
stopService.setOnClickListener {
//停止Service
val intent = Intent(this@MainActivity, MyServices::class.java)
stopService(intent)
}
这样一个简单的Service程序就写完了,接下来我们运行一下程序,点击startService并打开LogCat查看日志,如果正常的情况下会得到如下日志:
我们可以看到,启动时Service的onCreate
和onStartCommand
方法被调用了,再次点击以下startService按钮。
仔细观察我们可以发现,再次点击启动按钮之后Service的onCreate
没用被调用了,这是因为onCreate方法只会在Service第一次被创建的时候被调用。 点击停止按钮,我们就可以将Service踢掉了,查看日志如下:
以上,我们就完成了一个简单的后台Service创建流程。
绑定服务
绑定服务是客户端-服务器接口中的服务器。它允许 activity 等组件绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。
绑定服务是 Service
类的实现,可让其他应用与其绑定并进行交互。如需为服务提供绑定,需要实现 onBind()
回调方法。此方法会返回一个 IBinder
对象,该对象定义的编程接口可供客户端用来与服务进行交互。
在实现绑定服务的过程中,最重要的环节是定义 onBind()
回调方法所返回的接口。总共有三种定义服务 IBinder
接口的方法。
- 扩展IBinder类
- 使用Messenger
- 使用AIDL
对于大多数应用而言,AIDL方式不是创建绑定服务的好方法,因为其使用过于复杂,所以这里我们只介绍另外两种方法,下面还是通过例子来说明。
扩展Binder类
如果没有跨进程的需求,使用这种方法。如果只是本应用使用当前Service
,需要优先使用这种方式。
1.创建一个Service类
具体代码如下代码:
kotlin
class MyServices : Service() {
//2.创建Binder实例
private val mBinder = BinderImp()
fun doSomething() {
Log.i(TAG,"start doing something")
}
······
override fun onBind(intent: Intent?): IBinder {
//3.返回Binder实例给客户端
return mBinder
}
//1.扩展Binder类
inner class BinderImp :Binder() {
//4.获取Service实例进而获取其公有方法
fun getService():MyServices {
return this@MyServices
}
}
}
以上代码我们创建了一个内部类扩展Binder
类并创建了一个它的实例,接下来从onBind()
返回其实例来创建接口,客户端收到Binder
之后(onServiceConnected()
回调接收),可通过它直接访问Binder
实例或Service
中提供的共有方法。
2.在AndroidMainifest.xml申明
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
......
>
<service android:name=".MyServices"/>
......
</application>
</manifest>
3.添加操作布局按钮
xml
<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service" />
<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service"/>
4.服务绑定与解绑
然后在MainActivity中添加bind和unBind代码,调用 bindService()
来检索该接口,并开始对服务调用方法。Service
的生命周期与与其绑定的应用组件挂钩,因此,如果没有任何组件绑定到该Service
,系统将销毁该Service
。
kotlin
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
}
private val binding by binding(ActivityMainBinding::inflate)
private val connection = object :ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i(TAG,"onServiceConnected")
if (service is MyServices.BinderImp) {
val binderImp = service as MyServices.BinderImp
binderImp.getService().doSomething()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i(TAG,"onServiceDisconnected")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
.......
binding.bindService.setOnClickListener {
val intent = Intent(this@MainActivity, MyServices::class.java)
bindService(intent,connection, BIND_AUTO_CREATE)
}
binding.unbindService.setOnClickListener {
unbindService(connection)
}
}
}
关于绑定
binService
方法有三个参数
- 第一个参数是intent对象,用于显式命名要绑定的服务
- 第二个参数是我们创建的ServiceConnection实例
- 指示绑定选项(通常为
BIND_AUTO_CREATE
),用于在服务尚未处于活动状态时创建服务。其他可能的值为BIND_DEBUG_UNBIND
、BIND_NOT_FOREGROUND
或0
(表示无)。
另外两个参数好理解,这里讲一下第二个参数,我们创建了一个ServiceConnection
对象,重写了它的onServiceConnected
和onServiceDisconnected
方法,这两个方法分别在Service与Activity建立联系和解除联系的时候调用。一切准备就绪,点击bindService按钮之后日志如下:
可以看到,MyServices中的onCreate
和onBind
方法被调用,这时Service已经和MainActivity组件绑定上了并触发了onServiceConnected
回调。在onServiceConnected
中我们通过向下转型获取到了MyServices#BinderImp的实例,通过这个实例,我们就可以调用MyServices或者MyServices#BinderImp中的任何public方法,上例代码我们就调用了MyService的doSomething
方法。
解除绑定
当我们通过binService
的方式启动Service的时候想要销毁这个Service,点击stopService的时候发现Service的onDestroy
方法没有调用,也就是说Service没有被销毁,那么怎么销毁bind方式启动的service呢?很简单,调用Activity的unbindService
方法,传入我们创建的ServiceConnection对象。日志如下:
但细心的同学可能就会发现,销毁的时候onServiceDisconnected方法并没有调用,因为这个方法只有在Service异常销毁的时候才会被调用(例如内存的资源不足时)。
注意:
当用户即点击了
startService
又点击了bindService
时,由于bindService启动标识时BIND_AUTO_CREATE,销毁的时候单独点击stopService或者unbinService都不会销毁Service,只有两个都调用时Service才会被销毁。
使用Messenger
如果我们想让Service与远程进程同行,可以优先考虑使用这个方法而不是去使用AIDL,原因有二
- 对于大多数应用,服务不需要执行多线程处理,因此使用
Messenger
可让服务一次处理一个调用。 - 使用简单
如果您的服务必须执行多线程处理,请使用 AIDL 定义接口。
1.创建一个Service类
kotlin
/** Command to the service to display a message. */
const val MSG_SAY_HELLO = 1
class MessengerService : Service() {
companion object {
const val TAG = "MessengerService"
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private lateinit var mMessenger: Messenger
/**
* Handler of incoming messages from clients.
*/
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> {
Log.i(TAG,"hello${ Thread.currentThread().name}")
}
else -> super.handleMessage(msg)
}
}
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
override fun onBind(intent: Intent): IBinder? {
Log.i(TAG,"onBind")
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
以上首先我们创建了一个MessengerService
类继承自Service
;其次实现了一个Handler
(IncomingHandler)用来处理从客户端发送过来的消息;然后在onBind
方法中通过Messenger
创建了一个IBinder
并将其返回给客户端;最后定义了一个消息TAG用于在客户端创建消息。
2.在AndroidMainifest.xml申明
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
......
>
<service android:name=".MessengerService"/>
......
</application>
</manifest>
3.添加发送消息按钮
xml
<Button
android:id="@+id/send_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service" />
4.客户端(应用组件例如Activity)发送消息
kotlin
import com.abiao.services.MSG_SAY_HELLO
class SecondActivity : AppCompatActivity() {
private var mService:Messenger? = null
private var mIsBind = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i(TAG,"onServiceConnected:${Thread.currentThread().name}")
mService = Messenger(service)
mIsBind = true
}
override fun onServiceDisconnected(name: ComponentName?) {
mService = null
mIsBind = false
Log.i(TAG,"onServiceDisconnected")
}
}
companion object {
const val TAG = "SecondActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
sendMessage.setOnClickListener {
Log.i(TAG,"sendMessage")
if (!mIsBind)return@setOnClickListener
val message = Message.obtain(null,MSG_SAY_HELLO,0,0)
try{
mService?.send(message)
}catch (e:RemoteException) {
e.printStackTrace()
}
}
}
override fun onStart() {
super.onStart()
Intent(this,MessengerService::class.java).also { intent ->
bindService(intent,connection,Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
if (mIsBind) {
unbindService(connection)
mIsBind = false
}
}
}
以上首先我们创建了一个ServiceConnection
对象,区别是在我们在onServiceConnected
中根据服务中回调过来的IBinder创建了一个Messenger
实例,在onServiceDisconnected
释放Messenger
实例;然后分别在onStart
和onStop
方法中绑定服务和解绑服务;最后在点击事件里将Message
发送给服务。点击按钮我们可以看到如下日志:
到这里我们就创建好了一个Messenger
类型的Service了。
总结
以上就是关于Service的一些常用用法了,另外还有前台服务和绑定服务的AIDL类型,我们将在下一篇继续。