Android Service基础

Android Service 完全入门指南

1. 什么是 Service?

Service(服务)是 Android 四大组件之一,专门用来在后台执行长时间运行的操作 ,它不提供用户界面。比如:即使你切换到其他 App,音乐播放器仍能继续放歌;或者你在后台下载文件,用户仍可操作其他界面。

关键点:

  • Service 运行在主线程 (UI 线程)上,默认不是在独立线程或进程中。

  • 如果要在 Service 里做耗时任务(网络请求、文件读写),必须自行开启线程,否则会 ANR(应用无响应)。

  • Service 的优先级比后台 Activity 高,系统更不容易杀掉它。

2. Service 的三种类型

根据使用方式和可见性,Service 主要分为三类:

类型 说明 典型应用
前台服务 (Foreground Service) 会显示一个不可消除的通知,用户明确感知到它在运行。优先级高,几乎不会被系统杀死。 音乐播放、导航、运动追踪
后台服务 (Background Service) 不显示通知,用户可能不知道它在运行。在 Android 8.0 后受到严格限制。 数据同步、静默清理缓存(已逐渐被 WorkManager 取代)
绑定服务 (Bound Service) 允许其他组件(如 Activity)通过绑定方式与其交互。当所有组件解绑后,服务销毁。 Activity 与服务通信,如获取播放进度、控制播放

一个 Service 可以同时是"启动服务"和"绑定服务",既可被启动,也可被绑定。

3. Service 的生命周期

理解生命周期是正确使用 Service 的关键。取决于你如何启动它:

3.1 启动服务 (Started Service)

通过 startService() 启动,拥有自己的生命周期,与启动者无关。

text

复制代码
onCreate() → onStartCommand() → Service running → onDestroy()
  • onCreate():服务首次创建时调用,做一次初始化(只执行一次)。

  • onStartCommand() :每次 startService() 都会触发,你可以在此处理 Intent 传入的数据。

  • onDestroy():服务销毁前调用,做清理工作(释放线程、取消监听等)。

这种服务必须通过 stopSelf()stopService() 来停止。

3.2 绑定服务 (Bound Service)

通过 bindService() 绑定,生命周期与客户端绑定。

text

复制代码
onCreate() → onBind() → Clients are bound → onUnbind() → onDestroy()
  • onCreate():初始化。

  • onBind() :返回一个 IBinder,客户端通过它与服务交互。

  • onUnbind():所有客户端解绑时调用。

  • onDestroy():服务销毁。

若同时 start 和 bind 了一个服务,则必须取消绑定并调用 stopService()/stopSelf() 才会销毁。

4. 声明与权限

所有 Service 必须在 AndroidManifest.xml 中注册:

xml

复制代码
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false" />
  • exported:是否允许其他应用访问。通常设为 false

  • 前台服务还必须声明权限(Android 9+):

xml

复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

从 Android 14 (API 34) 开始,还需要指定前台服务类型:

xml

复制代码
<service
    android:name=".MyForegroundService"
    android:foregroundServiceType="location" />

以及对应的权限,如 ACCESS_FINE_LOCATION

5. 创建并使用不同类型 Service

5.1 启动服务(后台服务)

步骤 1:创建类继承 Service

kotlin

复制代码
class MyBackgroundService : Service() {

    override fun onCreate() {
        super.onCreate()
        // 初始化工作
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 在后台线程执行任务
        Thread {
            // 模拟耗时操作
            Thread.sleep(3000)
            // 任务完成后记得停止服务
            stopSelf()
        }.start()
        // 如果系统因内存不足杀掉服务,让系统在内存允许时重建服务并重新调用 onStartCommand
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

返回值说明(onStartCommand 返回值):

  • START_STICKY:服务被杀死后系统会尝试重新创建,但 Intent 为 null,适合播放音乐等。

  • START_NOT_STICKY:被杀死后不重新创建,除非有未处理的 Intent。

  • START_REDELIVER_INTENT:重新创建且重新传递之前的 Intent,适合下载类任务。

步骤 2:在 Activity 中启动/停止

kotlin

复制代码
val intent = Intent(this, MyBackgroundService::class.java)
startService(intent)   // 启动
// 停止服务
stopService(intent)

从 Android 8.0(API 26)开始,后台服务启动有严格限制:如果应用不在前台,调用 startService() 会抛异常。此时应使用 startForegroundService() 并迅速升级为前台服务,或者改用 WorkManager/JobScheduler。

5.2 前台服务

前台服务必须有一个持续存在的通知,告知用户服务正在运行。

步骤 1:创建通知渠道(Android 8.0+ 必须)

kotlin

复制代码
private fun createNotificationChannel() {
    val channel = NotificationChannel(
        "my_channel_id",
        "前台服务通知",
        NotificationManager.IMPORTANCE_LOW
    )
    val manager = getSystemService(NotificationManager::class.java)
    manager.createNotificationChannel(channel)
}

步骤 2:构建通知

kotlin

复制代码
val notification = NotificationCompat.Builder(this, "my_channel_id")
    .setContentTitle("正在播放音乐")
    .setContentText("歌曲名:晴天")
    .setSmallIcon(R.drawable.ic_music)
    .build()

步骤 3:在前台服务中启动

kotlin

复制代码
class MyForegroundService : Service() {
    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(1, notification) // 1是通知ID,唯一即可
        // 执行任务...
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // 可调用 stopForeground(true) 移除通知(true表示移除通知)
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

启动前台服务(Android 8.0+必须用 startForegroundService):

kotlin

复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent)
} else {
    startService(intent)
}

停止前台服务:

kotlin

复制代码
stopService(intent)

5.3 绑定服务

当你需要让 Activity 与服务直接通信(调用方法、获取数据)时使用。

步骤 1:创建服务,返回 Binder

kotlin

复制代码
class MyBindService : Service() {
    // 1. 创建内部Binder类
    inner class LocalBinder : Binder() {
        fun getService(): MyBindService = this@MyBindService
    }

    private val binder = LocalBinder()

    // 2. onBind 返回 binder
    override fun onBind(intent: Intent?): IBinder = binder

    // 3. 提供给客户端的公共方法
    fun getCurrentPosition(): Int = 12345
}

步骤 2:在 Activity 中绑定并连接

kotlin

复制代码
class MainActivity : AppCompatActivity() {
    private var boundService: MyBindService? = null
    private var isBound = false

    // 连接管理
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as MyBindService.LocalBinder
            boundService = binder.getService()
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
            boundService = null
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent(this, MyBindService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }

    fun useService() {
        // 调用服务方法
        val pos = boundService?.getCurrentPosition()
    }
}

6. 别再用 IntentService 了

IntentService 是 Service 子类,自动在工作线程处理队列化的 Intent,处理完自动停止。但它在 Android 11 (API 30) 已被弃用,因为缺少对后台启动限制的适配,且不够灵活。

替代方案:

  • WorkManager:适合可延迟、但必须执行的后台任务(上传日志、定期同步)。

  • JobIntentService(已废弃,建议 WorkManager)。

  • 手动创建 Service + Thread / Coroutine

简而言之,初学者可直接用 Service + Kotlin 协程,或使用 WorkManager。

7. Service 与 WorkManager 的选择

场景 推荐方案
必须有通知,用户能感知,不允许被杀死 前台服务
与当前界面交互,获取数据/控制 绑定服务
后台任务,可延迟,需保证执行(即使应用退出) WorkManager
一次性简单后台任务,应用在前台即可 简单协程 / 在 Activity 里执行

WorkManager 使用示例(对比 Service):

kotlin

复制代码
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
    .build()
WorkManager.getInstance(context).enqueue(uploadWorkRequest)

8. 常见问题与注意事项

8.1 主线程与 ANR

Service 的 onStartCommand()onBind() 等生命周期方法都运行在主线程。必须将耗时操作放到子线程(使用 Thread、ExecutorService、Kotlin Coroutines 等)。

8.2 Android 后台执行限制

  • Android 8.0+:不能随意后台启动服务,需改为前台服务。

  • Android 12+ :后台启动的前台服务会有延迟限制且需要指定 foregroundServiceType

  • 当应用从 Recent 中滑掉,后台服务可能被杀死。

最佳实践:如非必要,避免使用长时间后台服务,考虑 WorkManager + 前台服务通知配合。

8.3 内存泄漏

绑定服务一定要在合适的生命周期(如 onStop()onDestroy())里解绑,否则会泄漏 Activity。

8.4 前台服务类型(必需,Android 14+)

从 Android 14 开始,必须在清单中声明 foregroundServiceType,如 locationmediaPlayback 等,并请求对应权限。

xml

复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
...
<service android:name=".MyService"
    android:foregroundServiceType="location"
    android:exported="false"/>

9. 完整示例:简易音乐播放器服务

结合前台服务 + 绑定服务的设计,提供播放控制和进度获取。

MusicService.kt

kotlin

复制代码
class MusicService : Service() {
    private val binder = LocalBinder()
    private var isPlaying = false

    inner class LocalBinder : Binder() {
        fun getService(): MusicService = this@MusicService
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(NOTIFICATION_ID, buildNotification())
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder = binder

    fun play() {
        isPlaying = true
        // 媒体播放逻辑
    }

    fun pause() {
        isPlaying = false
    }

    fun isPlaying(): Boolean = isPlaying

    private fun buildNotification(): Notification {
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Music Player")
            .setContentText(if (isPlaying) "Playing..." else "Paused")
            .setSmallIcon(R.drawable.ic_music)
            .build()
    }

    companion object {
        const val CHANNEL_ID = "music_channel"
        const val NOTIFICATION_ID = 101
    }

    private fun createNotificationChannel() {
        val channel = NotificationChannel(
            CHANNEL_ID,
            "Music",
            NotificationManager.IMPORTANCE_LOW
        )
        getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 释放资源
    }
}

在 Activity 中使用

kotlin

复制代码
class MusicActivity : AppCompatActivity() {
    private var musicService: MusicService? = null
    private var isBound = false

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as MusicService.LocalBinder
            musicService = binder.getService()
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_music)

        val intent = Intent(this, MusicService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent)
        } else {
            startService(intent)
        }
        bindService(intent, connection, BIND_AUTO_CREATE)
    }

    fun onPlayClick(view: View) {
        musicService?.play()
    }

    override fun onDestroy() {
        super.onDestroy()
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }
}

10. 总结

  • Service 是 Android 后台能力的核心,但权限和限制已逐年收紧。

  • 新手上路建议:先学会 前台服务 + 绑定通信,理解生命周期后再深入 WorkManager。

  • 遵守 Android 后台执行规则,保证应用的流畅和用户的电池体验。


推荐延伸阅读:

相关推荐
ECT-OS-JiuHuaShan2 小时前
功夫不负匠心人,渡劫代谢舞沧桑
android·开发语言·人工智能·算法·机器学习·kotlin·拓扑学
ZC跨境爬虫4 小时前
移动端爬虫工具Fiddler完整配置流程:PC+安卓模拟器全覆盖,零基础一次配置成功
android·前端·爬虫·测试工具·fiddler
巴德鸟4 小时前
DaVinci 常用技巧 关键帧 自动字幕 追踪 音频 冻结帧 快捷键 多轨道字幕 扩充边缘
android·编辑器·音视频·视频·davinci·davin
学习使我健康5 小时前
Android 广播介绍详情
android·开发语言·kotlin
dalancon5 小时前
AudioTrack Start 执行流程分析
android
众少成多积小致巨6 小时前
Android 初始化语言入门
android·linux·c++
Carson带你学Android6 小时前
谁才是地表最强 Android Agent 大模型?Google官方测评来了!
android·openai
followYouself6 小时前
ASM开源库实现函数耗时插桩
android·asm·asm插桩·字节码插桩
TO_ZRG6 小时前
Android Content Provider 基础
android·jvm·oracle