《Android 核心组件深度系列 · 第 2 篇 Service》
继上一篇我们彻底吃透了 Activity,
今天我们来搞懂 Android 的第二大组件 ------ Service。
它不显示界面、不与用户交互,却是许多后台逻辑的灵魂所在。
不论你是写音乐播放器、下载器、IM 聊天,还是前台保活应用,
这篇都值得你收藏。
一、什么是 Service?
一句话定义:
Service 是一个在后台长时间运行的组件,用于执行不需要界面的任务。
简单来说:
- Activity → 用户界面交互
 - Service → 后台逻辑执行
 
举几个典型例子:
- 播放音乐(QQ 音乐、网易云)
 - 文件下载(迅雷、百度网盘)
 - 定位追踪(高德地图、美团外卖)
 - 长连接推送(微信、钉钉)
 
二、Service 的分类
| 类型 | 说明 | 示例 | 
|---|---|---|
| 普通服务(Started Service) | 调用 startService() 启动,执行完自动结束 | 
文件上传、数据同步 | 
| 绑定服务(Bound Service) | 调用 bindService(),与客户端双向通信 | 
音乐播放、蓝牙连接 | 
| 前台服务(Foreground Service) | 系统优先级最高,带通知栏 | 音乐播放、导航定位 | 
三、Service 生命周期详解
            
            
              scss
              
              
            
          
          onCreate() → onStartCommand() → 运行中 → onDestroy()
        | 方法 | 作用 | 
|---|---|
onCreate() | 
初始化服务,只调用一次 | 
onStartCommand() | 
每次通过 startService() 启动都会调用 | 
onDestroy() | 
服务销毁时调用 | 
四、普通后台服务示例
场景:上传文件到服务器
文件:UploadService.kt
        
            
            
              kotlin
              
              
            
          
          class UploadService : Service() {
    override fun onCreate() {
        super.onCreate()
        Log.d("ServiceDemo", "Service 创建了")
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("ServiceDemo", "Service 开始工作")
        // ⚠️ 注意:Service 默认运行在主线程!
        // 耗时操作必须放到子线程,不然会 ANR(应用无响应)
        Thread {
            for (i in 1..5) {
                Log.d("ServiceDemo", "正在上传文件 $i/5")
                Thread.sleep(1000)
            }
            stopSelf() // 任务完成,自己关闭自己
        }.start()
        // 返回值很重要!决定 Service 被系统杀死后怎么办
        return START_STICKY // 被杀后系统会尝试重启,但 intent 可能为空
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("ServiceDemo", "Service 销毁了")
    }
    override fun onBind(intent: Intent?): IBinder? {
        return null // 普通服务不需要绑定
    }
}
        onStartCommand 返回值详解
| 返回值 | 说明 | 适用场景 | 
|---|---|---|
START_STICKY | 
被杀后重启,但 intent 为 null | 音乐播放(不需要重传参数) | 
START_NOT_STICKY | 
被杀后不重启 | 一次性任务(如数据同步) | 
START_REDELIVER_INTENT | 
被杀后重启,并重传最后的 intent | 文件下载(需要断点续传) | 
MainActivity.kt
        
            
            
              kotlin
              
              
            
          
          class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.startServiceBtn).setOnClickListener {
            val intent = Intent(this, UploadService::class.java)
            startService(intent)
        }
        findViewById<Button>(R.id.stopServiceBtn).setOnClickListener {
            val intent = Intent(this, UploadService::class.java)
            stopService(intent)
        }
    }
}
        AndroidManifest.xml
        
            
            
              ini
              
              
            
          
          <service android:name=".UploadService" />
        五、前台服务:让系统不敢杀你
💡 为什么需要前台服务?
普通 Service 系统随时可能杀掉(省电、省内存)。
前台服务会在通知栏显示一个"小卡片",告诉用户"我正在工作",系统就不敢轻易杀了。
场景:音乐播放器
            
            
              kotlin
              
              
            
          
          class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Android 8.0 以上必须先创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "music_channel",
                "音乐播放",
                NotificationManager.IMPORTANCE_LOW // 低重要性,不会发出声音
            )
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(channel)
        }
        // 创建通知
        val notification = NotificationCompat.Builder(this, "music_channel")
            .setContentTitle("正在播放")
            .setContentText("周杰伦 - 晴天")
            .setSmallIcon(R.drawable.ic_music) // 必须有小图标
            .build()
        // 把自己变成"前台服务"
        startForeground(1, notification)
        return START_STICKY
    }
    override fun onBind(intent: Intent?): IBinder? = null
}
        📄 别忘了权限!
            
            
              xml
              
              
            
          
          <!-- Android 9 以上需要 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 以上还要指定类型 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<service 
    android:name=".MusicService"
    android:foregroundServiceType="mediaPlayback" />
        !!!!!注意!!! Android 12+ 的新限制
从 Android 12 开始,前台服务必须指定类型:
| 类型 | 说明 | 对应权限 | 
|---|---|---|
mediaPlayback | 
音乐、视频播放 | FOREGROUND_SERVICE_MEDIA_PLAYBACK | 
location | 
定位、导航 | FOREGROUND_SERVICE_LOCATION | 
dataSync | 
数据同步 | FOREGROUND_SERVICE_DATA_SYNC | 
六、绑定服务:Activity 和 Service 聊天
场景:控制音乐播放
有时候我们需要 Activity 和 Service "对话":
- Activity:下一首!
 - Service:好的,正在切歌~
 
这就是「绑定服务」。
MusicService.kt
        
            
            
              kotlin
              
              
            
          
          class MusicService : Service() {
    private val binder = LocalBinder()
    private var currentSong = "晴天"
    // 这是一个"遥控器",Activity 通过它控制 Service
    inner class LocalBinder : Binder() {
        fun getService(): MusicService = this@MusicService
    }
    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
    // 提供给 Activity 调用的方法
    fun playMusic() {
        Log.d("MusicService", "开始播放: $currentSong")
    }
    fun nextSong() {
        currentSong = "稻香"
        Log.d("MusicService", "切换到: $currentSong")
    }
    fun getCurrentSong(): String = currentSong
}
        MainActivity.kt
        
            
            
              kotlin
              
              
            
          
          class MainActivity : AppCompatActivity() {
    private var musicService: MusicService? = null
    private var isBound = false
    // 连接监听器
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            // 连接成功,拿到"遥控器"
            musicService = (binder as MusicService.LocalBinder).getService()
            isBound = true
            Log.d("MainActivity", "已连接到 Service")
        }
        override fun onServiceDisconnected(name: ComponentName?) {
            musicService = null
            isBound = false
            Log.d("MainActivity", "与 Service 断开连接")
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 绑定服务
        findViewById<Button>(R.id.bindBtn).setOnClickListener {
            val intent = Intent(this, MusicService::class.java)
            bindService(intent, connection, BIND_AUTO_CREATE)
        }
        // 控制播放
        findViewById<Button>(R.id.playBtn).setOnClickListener {
            musicService?.playMusic()
        }
        findViewById<Button>(R.id.nextBtn).setOnClickListener {
            musicService?.nextSong()
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        // ⚠️ 非常重要!不解绑会内存泄漏
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }
}
        七、跨进程通信:两个 App 之间怎么聊?
场景:调用第三方 App 的服务
有时候你的 App 需要和别的 App 的 Service 通信,比如:
- 调用支付宝的支付服务
 - 调用地图 App 的定位服务
 
这就需要「跨进程通信」,用 AIDL(听起来很吓人,其实就是定义接口的工具)。
简单理解
- 同一个 App 内:用 Binder(上面的例子)
 - 不同 App 之间:用 AIDL(高级版 Binder)
 
因为篇幅限制,这里先埋个坑,以后专门写一篇 AIDL 的文章 😄
八、Service vs IntentService vs WorkManager
🤔 我到底该用哪个?
| 组件 | 特点 | 适用场景 | 现状 | 
|---|---|---|---|
| Service | 需要手动开子线程 | 需要长期运行的任务 | 仍在用 | 
| IntentService | 自动开子线程,任务完成自动关闭 | 一次性后台任务 | 已废弃 | 
| WorkManager | 谷歌推荐,支持定时、重试、链式任务 | 延迟任务、定期任务 | 首选 | 
💡 建议
- 需要实时运行(如音乐播放)→ 用 Service
 - 定时任务(如每天凌晨同步数据)→ 用 WorkManager
 - 下载文件 → 用 WorkManager 或 DownloadManager
 
九、生命周期差异对比
| 特性 | 普通服务 | 前台服务 | 绑定服务 | 
|---|---|---|---|
| 是否长期运行 | ✅ | ✅ | ❌(依附于调用者) | 
| 是否能交互 | ❌ | ❌ | ✅ | 
| 系统回收概率 | 高 | 低 | 中等 | 
| 是否显示通知 | ❌ | ✅ | ❌ | 
| 适用场景 | 后台任务 | 音乐、定位 | 控制与通信 | 
十、常见坑点与避坑技巧
1️⃣ Android 8.0+ 后台启动限制
问题:在后台启动 Service 会报错
            
            
              vbnet
              
              
            
          
          IllegalStateException: Not allowed to start service Intent
        解决:
            
            
              scss
              
              
            
          
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent) // 用这个!
} else {
    startService(intent)
}
        记得在 onStartCommand() 里 5 秒内调用 startForeground(),不然会崩溃。
2️⃣ 忘记解绑导致内存泄漏
问题:Activity 销毁了,但 Service 还持有它的引用
解决:
            
            
              kotlin
              
              
            
          
          override fun onDestroy() {
    super.onDestroy()
    if (isBound) {
        unbindService(connection)
        isBound = false
    }
}
        3️⃣ Service 里做耗时操作导致 ANR
问题:Service 默认跑在主线程!
错误示范:
            
            
              kotlin
              
              
            
          
          override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // ❌ 主线程里下载文件,会卡死
    downloadFile("https://example.com/big_file.zip")
    return START_STICKY
}
        正确做法:
            
            
              kotlin
              
              
            
          
          override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // ✅ 开子线程
    Thread {
        downloadFile("https://example.com/big_file.zip")
        stopSelf()
    }.start()
    return START_STICKY
}
        更优雅的做法(用协程):
            
            
              kotlin
              
              
            
          
          private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    serviceScope.launch {
        downloadFile("https://example.com/big_file.zip")
        stopSelf()
    }
    return START_STICKY
}
override fun onDestroy() {
    super.onDestroy()
    serviceScope.cancel() // 取消所有协程
}
        4️⃣ 电池优化导致 Service 被杀
问题:小米、华为等手机会主动杀后台
解决:
- 引导用户把 App 加入"白名单"
 - 使用前台服务(被杀概率低很多)
 - 用 WorkManager 替代(系统级保障)
 
5️⃣ 前台服务没通知渠道导致崩溃
问题:Android 8.0+ 必须创建 NotificationChannel
解决:参考上面"前台服务"部分的代码
十一、进程保活的残酷真相
很多人问:"怎么让 Service 永远不被杀?"
坦白讲:在现代 Android 系统(8.0+)几乎不可能。
曾经的骚操作(已失效):
- ❌ 双进程守护
 - ❌ 1 像素 Activity 保活
 - ❌ 监听系统广播拉活
 
现在唯一靠谱的:
- ✅ 前台服务(带通知栏)
 - ✅ WorkManager(系统级保障)
 - ✅ 引导用户加白名单
 
记住:谷歌就是要杀后台,别和系统对着干 😅
十二、总结一句话
Service 是 Android 后台的灵魂。
它让你的 App 在用户"看不见"的时候继续活着。
学会用好 Service,你就掌握了 Android 的第二层生命。
核心要点回顾:
- Service 默认运行在主线程,耗时任务必须开子线程
 - 普通服务易被杀,前台服务带通知更安全
 - 绑定服务可以和 Activity 双向通信
 - Android 8.0+ 后台启动要用 
startForegroundService() - 记得解绑 Service,不然会内存泄漏
 - 优先考虑 WorkManager,别和系统对着干
 
十三、互动区
你写过最"难缠"的 Service 吗?
比如后台播放被杀、定位断开、保活不稳?
评论区聊聊你的血泪史 👇
下一篇预告 :《BroadcastReceiver:Android 的消息总线》
关注我,不错过每一篇硬核干货!