Android 中 Service 用法

Android 中 Service 用法

这篇就讲一件事:Service 到底什么时候用,怎么用,怎么不踩坑

你可以先把 Service 理解成:

"App 没有页面展示时,仍然需要在后台继续干活的一个组件"。

就像关注博主,下次容易找到一样。

点点关注一下

常见场景:

  • 音乐后台播放
  • 文件上传下载
  • 定位持续上报
  • 蓝牙设备连接与通信

1. 先记住这 3 句话

  1. Service 不是线程,它默认也跑在主线程。
  2. 耗时任务不能直接写在 Service 里,不然容易 ANR。
  3. 真正长期运行的后台任务,通常要做成前台服务(带通知)。

2. Service 有两种常用玩法

简单说:

  • 启动式:我让你去干活,你自己干完自己停。
  • 绑定式:我需要一直和你通信,比如"播放/暂停/下一首"。

2.1 启动式 Service(Started Service)

启动方式:

  • startService(intent)(老方式)
  • Android 8+ 更常见 startForegroundService(intent)(配合前台服务)

适合场景:

  • 同步数据
  • 上传日志
  • 一次性后台处理

核心回调:

  • onCreate():第一次创建时调用一次
  • onStartCommand():每次启动都会进来
  • onDestroy():销毁时调用

停止方式:

  • 在服务里 stopSelf()
  • 外部 stopService(intent)

2.2 绑定式 Service(Bound Service)

绑定方式:bindService(intent, conn, flags)

适合场景:

  • 音乐播放器控制
  • 下载进度回调
  • 页面和服务需要频繁交互

核心回调:

  • onCreate()
  • onBind()(返回 IBinder
  • onUnbind() / onRebind()
  • onDestroy()

一句话理解:

页面拿到 IBinder 后,就能像调用普通对象一样调用服务方法。


3. 生命周期怎么理解更简单

启动式

onCreate() -> onStartCommand()(可能多次)-> onDestroy()

onStartCommand() 返回值怎么选:

  • START_NOT_STICKY:被系统杀了就算了,不自动重建(一次性任务常用)
  • START_STICKY:系统尽量拉起来,但 Intent 可能没了
  • START_REDELIVER_INTENT:系统重建时,把上次 Intent 再给你一次

绑定式

onCreate() -> onBind() -> 交互 -> onUnbind() -> onDestroy()


4. 什么是前台服务(Foreground Service)

你可以把前台服务理解成:

"系统允许你长时间后台运行,但你必须给用户一个持续通知,告诉用户你在做什么。"

从 Android 8 开始,后台限制变严格,很多长期任务都要走前台服务。

基本要求:

  1. startForegroundService(intent) 启动
  2. 很快调用 startForeground(notificationId, notification)
  3. 通知必须可见(不能偷偷跑)

Android 14+ 额外注意:

  • Manifest 里写 foregroundServiceType
  • 类型要和实际任务对应(如 mediaPlaybacklocationdataSync
  • 相关权限要补齐(通知、定位等)

5. 代码示例(可直接参考)

5.1 启动式 Service(Kotlin)

kotlin 复制代码
class SyncService : Service() {

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    override fun onCreate() {
        super.onCreate()
        // 初始化资源
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        scope.launch {
            try {
                // 执行耗时任务
                doSync()
            } finally {
                // 任务结束后停止服务
                stopSelf(startId)
            }
        }
        return START_NOT_STICKY
    }

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

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

    private suspend fun doSync() {
        // TODO: 执行同步逻辑
    }
}

启动和停止:

kotlin 复制代码
ContextCompat.startForegroundService(context, Intent(context, SyncService::class.java))
// 或 context.startService(intent)(低版本或非前台需求)

context.stopService(Intent(context, SyncService::class.java))

5.2 绑定式 Service(Kotlin)

kotlin 复制代码
class MusicService : Service() {

    private val binder = LocalBinder()

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

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

    fun play() {
        // 播放逻辑
    }

    fun pause() {
        // 暂停逻辑
    }
}

Activity 绑定:

kotlin 复制代码
class MainActivity : AppCompatActivity() {

    private var musicService: MusicService? = null
    private var bound = false

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

        override fun onServiceDisconnected(name: ComponentName?) {
            bound = false
            musicService = null
        }
    }

    override fun onStart() {
        super.onStart()
        bindService(
            Intent(this, MusicService::class.java),
            conn,
            Context.BIND_AUTO_CREATE
        )
    }

    override fun onStop() {
        super.onStop()
        if (bound) {
            unbindService(conn)
            bound = false
        }
    }
}

6. Manifest 配置

xml 复制代码
<service
    android:name=".service.MusicService"
    android:exported="false"
    android:foregroundServiceType="mediaPlayback" />

说明:

  • android:exported="false":只给自己 App 用,默认更安全
  • 只有真的要给其他 App 调用,才考虑 exported="true",并加权限保护

7. 到底该不该用 Service

这些情况适合用 Service

  • 用户能感知到、而且要持续运行的任务(音乐、导航、运动)
  • App 切后台后,任务还要继续

这些情况不建议直接用 Service

  • 一次性、可延迟、要求"最终一定执行"的任务 -> 优先 WorkManager
  • 页面内短耗时任务 -> 协程 + 生命周期组件就够了

8. 常见问题与最佳实践

  1. 别在主线程做重活
    Service 默认是主线程,耗时操作请放协程/线程池。

  2. 干完就停

    任务完成后及时 stopSelf(),别让服务白跑。

  3. 前台服务别滥用

    有持续通知会打扰用户,必须有明确业务价值。

  4. 注意系统版本差异

    Android 8+ 后台限制、Android 13+ 通知权限、Android 14+ 前台服务类型都要适配。

  5. 考虑被系统杀进程

    按业务选好 onStartCommand() 返回值,并做好状态恢复。

  6. 跨进程通信是进阶题

    真要给其他 App 调用,通常走 AIDL + 权限控制。


9. 一句话总结

Service 适合"没页面但要持续干活"的任务:

只管执行用启动式 ,需要交互控制用绑定式 ,长期后台任务优先做前台服务并按系统版本补齐权限和类型声明。

相关推荐
故渊at10 分钟前
第一板块:Android 系统基石与运行原理 | 第三篇:ART 与 Dalvik 运行时环境原理
android·对象模型·内存布局·运行原理·art·dalvik
私人珍藏库1 小时前
【Android】Wallcraft 3.62.0-最强4 K壁纸软件-解锁高级版
android·智能手机·app·工具·软件·多功能
GesLuck2 小时前
Node-RED企业微信发送—群文件
android·java·企业微信
whatever who cares2 小时前
android中fragment demo举例
android·java·开发语言
zhangphil2 小时前
Android将ImageView显示的图原样取出转换为Bitmap,Kotlin
android·kotlin
plainGeekDev2 小时前
CountDownTimer → Flow
android·java·kotlin
仙俊红2 小时前
如何优化 MySQL 深分页 SQL
android·sql·mysql
awu的Android笔记3 小时前
网络闪断 + DNS 故障:Android弱网模拟中最容易被忽视的两个场景
android·tcp/ip
Flynt3 小时前
Android 17内存限制:我是怎么发现App被系统悄悄干掉的
android·性能优化
消失的旧时光-19434 小时前
Kotlin 协程设计思想(七):为什么 Kotlin 要设计 SupervisorJob 和 supervisorScope?
android·开发语言·kotlin