Service服务是四大组件之一,在Android中实现程序后台运行的解决方案,适用于去执行那些不需要和用户交互而且还要求长期运行的任务。
Service特点
startService 和 bindService 是启动或连接服务的俩种截然不同的方式,简单来说:
startService:用于启动一个在后台长期运行的服务,没有返回结果
bindService:用于建立一个与服务连接,便于进行交互和通信
区别
为了更直观地理解,核心区别整理成了下面的表格:
| 特性维度 | startService (启动服务) | bindService (绑定服务) |
|---|---|---|
| 核心需求 | 在后台执行一个任务(如:下载文件、播放音乐) | 建立客户端-服务器连接,与服务进行交互、调用其方法、获取数据 |
| 生命周期 | 独立于调用者。即使启动它的 Activity 被销毁,服务也能在后台无限期运行 | 与调用者绑定。当所有绑定的客户端都解除绑定后,服务即被销毁 |
| 通信方式 | 单向通信。服务无法直接返回结果给调用者,通常通过广播、通知等方式反馈 | 双向通信。通过 onBind() 返回的 IBinder 接口,客户端可以直接调用服务的方法 |
| 典型回调方法 |
onCreate() -> onStartCommand() -> onDestroy()
|
onCreate() -> onBind() -> onUnbind() -> onDestroy()
|
startService 的生命周期
- 首次启动: 当首次调用
onstartService()时,系统会创建服务实例,依次执行onCreate()和onStartCommand()方法 - 多次启动:如果服务已经启动,再次调用
startService()不会再次创建服务,其"onCreate()方法不会再次调用",但是每次会触发onStartCommand()方法,可以通过Intent的参数传递不同的指令 - 终止服务:服务必须通过调用
stopService()[外部方法] 或stopSelf()[内部]来显示停止。此时系统会调用onDestroy
bindService 的生命周期:
- 首次绑定:当首次调用
bindService()时,系统会创建服务(如果尚未创建),并依次调用onCreate()和onBind()方法 - 多次绑定:如果有多个客户端绑定同一个服务,服务只会创建一次。
onCreate()和onBind()也只会调用一次。系统会通过同一个Ibinder对象与所有客户端通信 - 终止服务:当所有的客户端都调用
unBindService()解除绑定后,服务会自动调用onUnbind()和onDestroy进行销毁
startService 启动代码
注册服务xml
<service android:name=".services.DownloadService" />
// DownloadService.kt 下载的服务
// DownloadService.kt
package com.example.myinterview.services
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
class DownloadService : Service() {
companion object {
const val TAG = "DownloadService"
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: 服务创建")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val url = intent?.getStringExtra("url") ?: "default_url"
Log.d(TAG, "onStartCommand: 开始下载,url=$url")
// 模拟下载任务(实际应放在子线程)
Thread {
// 执行下载
download(url)
// 下载完成后自行停止服务,内部结束服务
stopSelf()
}.start()
// 返回 START_NOT_STICKY 表示系统在服务被意外杀死后不再重启
return START_NOT_STICKY
}
private fun download(url: String) {
// 模拟耗时操作 10秒
Thread.sleep(1000*10)
Log.d(TAG, "下载完成: $url")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: 服务销毁")
}
override fun onBind(intent: Intent): IBinder? {
// 纯启动服务不绑定,返回 null
return null
}
}
// MainActivity 启动
package com.example.myinterview
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.myinterview.services.DownloadService
import kotlin.concurrent.thread
class MainActivity : AppCompatActivity() {
lateinit var intentDownloadService: Intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
findViewById<Button>(R.id.onStartService).setOnClickListener {
intentDownloadService = Intent(this, DownloadService::class.java)
log("Button ==> onStartService")
onStartService()
}
findViewById<Button>(R.id.onStopService).setOnClickListener {
log("Button ==> onStopService")
onStopService()
}
}
fun onStartService() {
// 启动服务
intentDownloadService.putExtra("url", "https://example.com/file.zip")
startService(intentDownloadService)
}
fun onStopService() {
// 停止服务(通常在某个按钮点击时调用)
stopService(intentDownloadService)
}
fun log(message: String) {
// 添加日志记录功能
Log.e(javaClass.simpleName, message)
}
}
- 开始 onStartService()
2026-03-21 20:01:17.315 13214-13214 MainActivity com.example.myinterview E Button ==> onStartService
2026-03-21 20:01:17.318 13214-13214 DownloadService com.example.myinterview D onCreate: 服务创建
2026-03-21 20:01:17.318 13214-13214 DownloadService com.example.myinterview D onStartCommand: 开始下载,url=https://example.com/file.zip
2026-03-21 20:01:20.309 13214-13258 ProfileInstaller com.example.myinterview D Installing profile for com.example.myinterview
2026-03-21 20:01:27.320 13214-13254 DownloadService com.example.myinterview D 下载完成: https://example.com/file.zip
2026-03-21 20:01:27.323 13214-13214 DownloadService com.example.myinterview D onDestroy: 服务销毁
总结:正常生命周期,执行任务后内部stopSelf()结束服务
- 开始 onStartService() 3秒后-> 结束 onStopService()
2026-03-21 20:01:47.124 13214-13214 MainActivity com.example.myinterview E Button ==> onStartService
2026-03-21 20:01:47.135 13214-13214 DownloadService com.example.myinterview D onCreate: 服务创建
2026-03-21 20:01:47.136 13214-13214 DownloadService com.example.myinterview D onStartCommand: 开始下载,url=https://example.com/file.zip
2026-03-21 20:01:50.220 13214-13214 MainActivity com.example.myinterview E Button ==> onStopService
2026-03-21 20:01:50.224 13214-13214 DownloadService com.example.myinterview D onDestroy: 服务销毁
2026-03-21 20:01:57.138 13214-13289 DownloadService com.example.myinterview D 下载完成: https://example.com/file.zip
总结:正常生命周期,3秒后 执行外部 stopService(intentDownloadService) 结束服务
- 开始 onStartService() 3秒后-> 开始 onStartService()
2026-03-21 20:02:55.980 13214-13214 MainActivity com.example.myinterview E Button ==> onStartService
2026-03-21 20:02:55.987 13214-13214 DownloadService com.example.myinterview D onCreate: 服务创建
2026-03-21 20:02:55.998 13214-13214 DownloadService com.example.myinterview D onStartCommand: 开始下载,url=https://example.com/file.zip
2026-03-21 20:02:58.602 13214-13214 MainActivity com.example.myinterview E Button ==> onStartService
2026-03-21 20:02:58.607 13214-13214 DownloadService com.example.myinterview D onStartCommand: 开始下载,url=https://example.com/file.zip
2026-03-21 20:03:06.000 13214-13316 DownloadService com.example.myinterview D 下载完成: https://example.com/file.zip
2026-03-21 20:03:06.003 13214-13214 DownloadService com.example.myinterview D onDestroy: 服务销毁
2026-03-21 20:03:08.610 13214-13318 DownloadService com.example.myinterview D 下载完成: https://example.com/file.zip
总结:执行多次 onStartService , 第一次启动正常生命周期会创建onCreate,后面执行onStartCommand
int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) 返回值
根据返回值,用于指定服务被系统意外杀死后的行为
| 返回值 | 服务被杀死后 | Intent 是否保留 | 适用场景 |
|---|---|---|---|
| START_NOT_STICKY | ❌ 不重启 | ❌ 不保留 | 一次性任务(如下载) |
| START_STICKY | ✅ 重启 | ❌ 不保留(Intent 为 null) | 后台持续任务(如音乐播放) |
| START_REDELIVER_INTENT | ✅ 重启 | ✅ 保留并重发 | 必须完成的任务(如文件上传) |
| START_CONTINUATION_MASK | ⚠️ 仅标志位,不单独使用 | X | 与 START_STICKY 组合使用,表示服务是连续任务的一部分 |
BindService启动代码
注册服务xml
<service android:name=".services.StepCounterService" />
// StepCounterService.kt 计步器服务
package com.example.myinterview.services
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
class StepCounterService : Service() {
private val binder = LocalBinder()
private var stepCount = 0
private var countingThread: Thread? = null
private var isCounting = true
inner class LocalBinder : Binder() {
fun getService(): StepCounterService = this@StepCounterService
}
override fun onCreate() {
super.onCreate()
Log.d("StepCounter", "onCreate: 服务创建")
// 模拟开始计步(实际可监听传感器)
startCounting()
}
override fun onBind(intent: Intent): IBinder {
Log.d("StepCounter", "onBind: 客户端绑定")
return binder
}
override fun onUnbind(intent: Intent): Boolean {
Log.d("StepCounter", "onUnbind: 所有客户端解绑")
return false // 返回 false 表示不再期待重新绑定
}
override fun onDestroy() {
super.onDestroy()
Log.d("StepCounter", "onDestroy: 服务销毁")
// 停止线程
isCounting = false
countingThread?.interrupt()
countingThread = null
}
// 供客户端调用的公共方法
fun getStepCount(): Int = stepCount
private fun startCounting() {
// 模拟步数增长
countingThread = Thread {
while (isCounting) {
try {
Thread.sleep(2000)
if (isCounting) {
stepCount += (1..10).random()
Log.d("StepCounter", "当前步数:$stepCount")
}
} catch (e: InterruptedException) {
Log.d("StepCounter", "线程被中断,停止计数")
break
}
}
}
countingThread?.start()
}
}
// MainActivity 启动
package com.example.myinterview
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.myinterview.services.StepCounterService
import kotlin.concurrent.thread
class MainActivity : AppCompatActivity() {
//计数器服务
private var stepService: StepCounterService? = null
//标识解绑状态
private var isBound = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
findViewById<Button>(R.id.onBinService).setOnClickListener {
intentDownloadService = Intent(this, DownloadService::class.java)
log("Button ==> onBinService")
onBinService()
}
findViewById<Button>(R.id.unOnBinService).setOnClickListener {
log("Button ==> unOnBinService")
if (isBound) {
unbindService(mServiceConnection)
isBound = false
}
}
}
fun onBinService() {
// 绑定服务
val intent = Intent(this, StepCounterService::class.java)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
}
// 服务连接回调
private val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as StepCounterService.LocalBinder
stepService = binder.getService()
isBound = true
// 绑定成功后,可以调用服务的方法
updateStepCount()
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
stepService = null
}
}
private fun updateStepCount() {
val textView: TextView = findViewById(R.id.textView_main)
// 模拟定期获取步数
thread {
while (isBound) {
Thread.sleep(1000)
runOnUiThread {
stepService?.let {
textView.text = "步数:${it.getStepCount()}"
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
// 必须解绑,否则会内存泄漏
if (isBound) {
unbindService(mServiceConnection)
isBound = false
}
}
fun log(message: String) {
// 添加日志记录功能
Log.e(javaClass.simpleName, message)
}
}
- 开始 bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
2026-03-21 20:21:30.444 15139-15139 MainActivity com.example.myinterview E Button ==> onBinService
2026-03-21 20:21:30.448 15139-15139 StepCounter com.example.myinterview D onCreate: 服务创建
2026-03-21 20:21:30.449 15139-15139 StepCounter com.example.myinterview D onBind: 客户端绑定
2026-03-21 20:21:32.450 15139-15212 StepCounter com.example.myinterview D 当前步数:1
2026-03-21 20:21:34.450 15139-15212 StepCounter com.example.myinterview D 当前步数:4
2026-03-21 20:21:36.451 15139-15212 StepCounter com.example.myinterview D 当前步数:12
2026-03-21 20:21:38.452 15139-15212 StepCounter com.example.myinterview D 当前步数:15
2026-03-21 20:21:40.453 15139-15212 StepCounter com.example.myinterview D 当前步数:17
2026-03-21 20:21:42.454 15139-15212 StepCounter com.example.myinterview D 当前步数:24
2026-03-21 20:21:44.454 15139-15212 StepCounter com.example.myinterview D 当前步数:28
2026-03-21 20:21:46.455 15139-15212 StepCounter com.example.myinterview D 当前步数:34
2026-03-21 20:21:48.456 15139-15212 StepCounter com.example.myinterview D 当前步数:37
2026-03-21 20:21:50.457 15139-15212 StepCounter com.example.myinterview D 当前步数:45
2026-03-21 20:21:52.457 15139-15212 StepCounter com.example.myinterview D 当前步数:53
总结:正常生命周期,一直获取计数服务中的数值
- 开始 onBinService() 5秒后-> 开始手动 unbindService(mServiceConnection) 解绑服务
2026-03-21 20:25:23.492 15540-15540 MainActivity com.example.myinterview E Button ==> onBinService
2026-03-21 20:25:23.497 15540-15540 StepCounter com.example.myinterview D onCreate: 服务创建
2026-03-21 20:25:23.498 15540-15540 StepCounter com.example.myinterview D onBind: 客户端绑定
2026-03-21 20:25:25.500 15540-15578 StepCounter com.example.myinterview D 当前步数:6
2026-03-21 20:25:27.501 15540-15578 StepCounter com.example.myinterview D 当前步数:12
2026-03-21 20:25:29.502 15540-15578 StepCounter com.example.myinterview D 当前步数:15
2026-03-21 20:25:30.134 15540-15540 MainActivity com.example.myinterview E Button ==> unOnBinService
2026-03-21 20:25:30.137 15540-15540 StepCounter com.example.myinterview D onUnbind: 所有客户端解绑
2026-03-21 20:25:30.138 15540-15540 StepCounter com.example.myinterview D onDestroy: 服务销毁
2026-03-21 20:25:30.139 15540-15578 StepCounter com.example.myinterview D 线程被中断,停止计数
总结:正常生命周期,5秒后 unBindService服务销毁, 模拟是线程中断trycatch
- 开始 bindService() 5秒后-> 继续再启动绑定 bindService() -->10秒后-> 手动解绑服务unbindService()
2026-03-21 20:29:03.867 15998-15998 MainActivity com.example.myinterview E Button ==> onBinService
2026-03-21 20:29:03.870 15998-15998 StepCounter com.example.myinterview D onCreate: 服务创建
2026-03-21 20:29:03.890 15998-15998 StepCounter com.example.myinterview D onBind: 客户端绑定
2026-03-21 20:29:05.874 15998-16038 StepCounter com.example.myinterview D 当前步数:7
2026-03-21 20:29:07.875 15998-16038 StepCounter com.example.myinterview D 当前步数:11
2026-03-21 20:29:09.875 15998-16038 StepCounter com.example.myinterview D 当前步数:16
2026-03-21 20:29:10.989 15998-15998 MainActivity com.example.myinterview E Button ==> onBinService
2026-03-21 20:29:11.876 15998-16038 StepCounter com.example.myinterview D 当前步数:24
2026-03-21 20:29:13.877 15998-16038 StepCounter com.example.myinterview D 当前步数:30
2026-03-21 20:29:15.878 15998-16038 StepCounter com.example.myinterview D 当前步数:37
2026-03-21 20:29:17.879 15998-16038 StepCounter com.example.myinterview D 当前步数:41
2026-03-21 20:29:19.880 15998-16038 StepCounter com.example.myinterview D 当前步数:45
2026-03-21 20:29:21.881 15998-16038 StepCounter com.example.myinterview D 当前步数:52
2026-03-21 20:29:23.882 15998-16038 StepCounter com.example.myinterview D 当前步数:61
2026-03-21 20:29:25.883 15998-16038 StepCounter com.example.myinterview D 当前步数:66
2026-03-21 20:29:27.417 15998-15998 MainActivity com.example.myinterview E Button ==> unOnBinService
2026-03-21 20:29:27.421 15998-15998 StepCounter com.example.myinterview D onUnbind: 所有客户端解绑
2026-03-21 20:29:27.422 15998-15998 StepCounter com.example.myinterview D onDestroy: 服务销毁
2026-03-21 20:29:27.423 15998-16038 StepCounter com.example.myinterview D 线程被中断,停止计数
总结:当前服务第一次绑定并且创建后,bindService() ,只有第一次有效,系统会忽略后续请求。
第二次点击按钮(关键!):
↓
⚠️ 不会调用 onCreate()(服务已存在)
↓
⚠️ 不会调用 onBind()(已经绑定)
↓
⚠️ 不会调用 onServiceConnected()(不会重复回调)
↓
❌ 什么都不会发生!
- 假设每次创建新的 Connection
// 假设每次创建新的 Connection
val conn1 = object : ServiceConnection { ... }
val conn2 = object : ServiceConnection { ... }
val conn3 = object : ServiceConnection { ... }
生命周期变化:
第 1 次 bindService(conn1):
→ onCreate() ✅
→ onBind() ✅
→ onServiceConnected(conn1) ✅
【绑定计数 = 1】
第 2 次 bindService(conn2):
→ (跳过 onCreate)
→ onBind() ✅ (重新调用)
→ onServiceConnected(conn2) ✅
【绑定计数 = 2】
第 3 次 bindService(conn3):
→ (跳过 onCreate)
→ onBind() ✅
→ onServiceConnected(conn3) ✅
【绑定计数 = 3】
解绑时的变化:
第 1 次 unbindService(conn1):
→ onUnbind()
→ (服务继续运行,因为还有 2 个连接)
【绑定计数 = 2】
第 2 次 unbindService(conn2):
→ onUnbind()
→ (服务继续运行,因为还有 1 个连接)
【绑定计数 = 1】
第 3 次 unbindService(conn3):
→ onUnbind()
→ onDestroy() ✅ (最后一个连接断开)
【绑定计数 = 0,服务销毁】
总结:不同 Connection 多次 bind,每次都有效,绑定计数累加
服务ServiceConnection要想销毁,必须所有的绑定计数器都解绑unbindService才能销毁
绑定参数 bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
-
第一个参数:Intent ,指定要绑定的服务
- 显式 Intent
- 也可以使用隐式 Intent(通过 Action 匹配)
- 可以携带额外数据(通过 putExtra())
-
第二个参数:ServiceConnection,监听服务连接状态的回调接口;两个关键方法
- onServiceConnected():绑定成功时调用,可获取服务的 IBinder 对象
- onServiceDisconnected():连接异常断开时调用(正常解绑不会触发)
- 第三个参数:flags,指定绑定服务的行为标志
| 标志 | 说明 |
|---|---|
| BIND_AUTO_CREATE | 自动创建服务(最常用)。如果服务未运行,系统自动调用 onCreate() |
| 0 | 不使用任何标志。服务必须已经运行,否则绑定失败 |
| BIND_DEBUG_UNBIND | 调试模式,检测未解绑的情况 |
| BIND_NOT_FOREGROUND | 服务不在前台运行 |
| BIND_ABOVE_CLIENT | 服务优先级高于客户端 |
| BIND_IMPORTANT | 重要绑定,提升服务优先级 |
| BIND_WAIVE_PRIORITY | 放弃优先级,服务更容易被回收 |
显示服务 vs 隐式服务
-
显示服务 (Explicit Service)
定义:明确指定要启动的 Service 类名,直接通过类名启动服务。// 显示启动服务 val intent = Intent(this, DownloadService::class.java) intent.putExtra("url", "https://example.com/file.apk") startService(intent)特点:
- 明确指定了服务类,Intent(context,ServiceClass::class.java)
- 通常用于应用内部的服务调用
- 编译时就能确定目标服务
- 安全性高,其他应用无法拦截
-
隐式服务 (Implicit Service)
定义:不指定具体的类名,通过Action,Category等过滤器匹配服务
//需要添加配置过滤器: <service android:name=".services.DownloadService" android:exported="false"> <intent-filter> <action android:name="com.example.myinterview.DOWNLOAD_SERVICE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="application/file" /> </intent-filter> </service>
隐式启动服务的调用
// 隐式启动服务 调用
val intent = Intent("com.example.myinterview.DOWNLOAD_SERVICE")
intent.type = "application/file"
intent.putExtra("url", "https://example.com/file.apk")
startService(intent)
特点:
- 通过Action/Category/Data匹配
- 可用于跨应用调用。需要设置 android:exported="true"
- 更灵活,支持动态匹配
- 需要声明 intent-filter
- 可能被其他应用拦截(需要注意安全)
从 Android 5.0 开始,系统不再允许使用隐式 Intent 启动 Service(包括 startService 和 bindService),除非 Intent 明确指定了包名或组件名。如果尝试用隐式 Intent 启动 Service,会抛出异常:
java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.example.myinterview.DOWNLOAD (has extras) }
原因:隐式 Intent 启动 Service 存在安全风险——恶意应用可能通过注册相同的 IntentFilter 劫持服务调用。为了增强安全性,Android 强制要求 Service 启动时使用显式 Intent(即明确指定 ComponentName 或 packageName)。
IntentFilter 隐示意图
| 组件类型 | 是否支持 IntentFilter | 隐式启动是否推荐/可用 |
|---|---|---|
| Activity | ✅ 支持 | 可用,常用于启动其他应用的页面 |
| Service | ✅ 支持(语法上) | 不推荐且受限(API 21+ 禁止隐式启动) |
| BroadcastReceiver | ✅ 支持 | 广泛使用,系统广播多为隐式 |