0
点赞
收藏
分享

微信扫一扫

Android service 使用

好的,我们来详细探讨 Android 中 Service 的使用。Service 是 Android 四大组件之一,用于在后台执行长时间运行的操作,且不提供用户界面。

Android Service 使用详解

一、 什么是 Service?
  • 定义: Service 是一个可以在后台执行长时间运行操作的组件,它没有用户界面
  • 运行环境: 默认情况下,Service 运行在主线程 (UI Thread) 中。因此,不能在 ServiceonStartCommand()onBind() 方法中直接执行耗时操作,否则会阻塞主线程,导致 ANR。
  • 生命周期: Service 的生命周期独立于启动它的组件(如 Activity)。即使启动它的 Activity 被销毁,Service 仍然可以继续运行(直到它自己停止或被系统杀死)。
  • 用途:
  • 播放音乐。
  • 下载文件。
  • 执行网络请求。
  • 与内容提供者交互。
  • 执行后台计算。
二、 Service 的启动方式

Service 可以通过两种方式启动,对应不同的生命周期和使用场景:

1. Started Service (启动式服务)
  • 目的: 用于执行一个单一的、长时间运行的操作。操作完成后,服务应该自己停止。
  • 启动方法: 使用 Context.startService(Intent)
  • 停止方法: 服务内部调用 stopSelf() 或其他组件调用 Context.stopService(Intent)
  • 生命周期:
  1. onCreate(): 服务首次创建时调用。在整个生命周期中只调用一次,适合进行一次性的初始化。
  2. onStartCommand(Intent, int, int): 每次调用 startService() 时都会调用此方法。这是执行耗时操作的主要入口。
  3. onDestroy(): 服务被销毁前调用。用于清理资源。
  • onStartCommand() 的返回值:
  • START_STICKY: 如果服务被系统杀死,系统会尝试重新创建服务并调用 onStartCommand(),但 Intentnull。适用于音乐播放器等需要持续运行的服务。
  • START_NOT_STICKY: 如果服务被杀死,系统不会重新创建服务。适用于不需要恢复的任务。
  • START_REDELIVER_INTENT: 如果服务被杀死,系统会重新创建服务并使用原来的 Intent 再次调用 onStartCommand()。适用于需要传递特定参数的任务(如下载特定文件)。
  • 代码示例:

class MyStartedService : Service() {
    override fun onCreate() {
        super.onCreate()
        // 初始化,如创建线程池
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 在主线程调用,必须将耗时操作移出主线程
        Thread {
            // 执行耗时任务,如下载
            downloadFile(intent?.getStringExtra("url"))
            // 任务完成后停止服务
            stopSelf()
        }.start()

        // 或者使用协程
        // CoroutineScope(Dispatchers.IO).launch {
        //     downloadFile(intent?.getStringExtra("url"))
        //     stopSelf()
        // }

        return START_STICKY // 根据需求选择
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null // Started Service 通常返回 null
    }

    override fun onDestroy() {
        super.onDestroy()
        // 清理资源
    }
}

2. Bound Service (绑定式服务)
  • 目的: 允许其他组件(客户端)绑定到服务,并与之进行交互(如调用服务的方法、发送请求、接收响应)。多个客户端可以同时绑定。当所有客户端都解绑后,服务通常会被销毁。
  • 启动方法: 使用 Context.bindService(Intent, ServiceConnection, int)
  • 停止方法: 所有客户端调用 Context.unbindService(ServiceConnection)。当没有客户端绑定时,服务自动停止。
  • 核心: IBinder 接口。服务必须在 onBind() 方法中返回一个 IBinder 对象,客户端通过这个对象与服务通信。
  • 生命周期:
  1. onCreate(): 服务创建。
  2. onBind(Intent): 客户端绑定时调用,返回 IBinder
  3. onUnbind(Intent): 所有客户端解绑后调用。
  4. onDestroy(): 服务被销毁。
  • 实现 IBinder 的方式:
  • 扩展 Binder 类 (推荐,简单): 适用于服务与客户端在同一进程。
  • 使用 Messenger: 基于 Handler,适用于跨进程通信 (IPC),但请求是串行处理的。
  • 使用 AIDL: 更强大的 IPC 方式,支持多线程并发访问,但配置复杂。
  • 代码示例 (Binder):

class MyBoundService : Service() {
    private val binder = LocalBinder()

    // 定义服务对外暴露的方法
    fun getCurrentTime(): String {
        return SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
    }

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

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        return super.onUnbind(intent)
    }
}

// 在 Activity 中使用
class MainActivity : AppCompatActivity() {
    private lateinit var service: MyBoundService
    private var isBound = false

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            val localBinder = binder as MyBoundService.LocalBinder
            service = localBinder.getService()
            isBound = true
            // 可以调用 service 的方法
            textView.text = service.getCurrentTime()
        }

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

    override fun onStart() {
        super.onStart()
        // 绑定服务
        Intent(this, MyBoundService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // 解绑服务
        if (isBound) {
            unbindService(connection)
            isBound = false
        }
    }
}

三、 前台服务 (Foreground Service)
  • 目的: 执行用户明确知晓的、重要的后台任务。它会显示一个持续的通知,让用户知道服务正在运行,并且不能轻易被系统杀死。
  • 使用场景: 音乐播放、位置追踪、文件下载(用户可见进度)。
  • 如何创建:
  1. ServiceonCreate()onStartCommand() 中调用 startForeground(int id, Notification notification)
  2. 必须提供一个 Notification,且该通知的优先级通常为 PRIORITY_LOW 或更高。
  3. 需要声明权限 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  • 停止: 调用 stopForeground(true) 会移除通知并可能将服务降级为普通后台服务。调用 stopSelf()stopService() 才能完全停止服务。
  • 代码示例:

class MyForegroundService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("My Service")
            .setContentText("Running...")
            .setSmallIcon(R.drawable.ic_service)
            .build()

        startForeground(NOTIFICATION_ID, notification)

        // 执行后台任务...
        return START_STICKY
    }

    // ... 其他方法
}

四、 服务的线程处理

关键点:Service 本身运行在主线程!

  • 错误做法:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // ❌ 错误!这会阻塞主线程,导致ANR
    Thread.sleep(10000)
    return START_STICKY
}

  • 正确做法:
  1. Service 内部创建新线程:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Thread {
        // 耗时操作
        performLongRunningTask()
        stopSelf() // 任务完成后停止
    }.start()
    return START_STICKY
}

  1. 使用 HandlerThread:

private lateinit var serviceLooper: Looper
private lateinit var serviceHandler: Handler

override fun onCreate() {
    val thread = HandlerThread("MyServiceThread")
    thread.start()
    serviceLooper = thread.looper
    serviceHandler = Handler(serviceLooper) { msg ->
        // 在后台线程执行
        performTask(msg)
        stopSelf(msg.arg1) // 使用 startId 停止
        true
    }
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val msg = serviceHandler.obtainMessage()
    msg.arg1 = startId // 用于在任务完成后停止正确的实例
    serviceHandler.sendMessage(msg)
    return START_STICKY
}

  1. 使用 ExecutorService:

private val executor = Executors.newSingleThreadExecutor()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    executor.execute {
        performLongRunningTask()
        stopSelf()
    }
    return START_STICKY
}

  1. 使用 Kotlin Coroutines:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    CoroutineScope(Dispatchers.IO).launch {
        performLongRunningTask()
        stopSelf()
    }
    return START_STICKY
}

五、 IntentService (已弃用)
  • 历史: IntentService 是一个 Service 的子类,内部使用 HandlerThread 来处理 Intent。每个 startService() 请求都会在后台线程排队执行,执行完毕后自动停止服务。
  • 优点: 简化了后台线程的管理。
  • 缺点: 被弃用。对于后台任务,应使用 WorkManagerJobScheduler
六、 最佳实践与注意事项
  1. 不要在主线程执行耗时操作:这是使用 Service 的首要原则。
  2. 选择合适的启动方式
  • 只需执行任务 -> Started Service
  • 需要交互 -> Bound Service
  • 用户可见的重要任务 -> Foreground Service
  1. 及时停止服务Started Service 在任务完成后应调用 stopSelf()
  2. 管理绑定Bound Service 要确保在 onStop()onDestroy() 中解绑,防止内存泄漏。
  3. 使用现代替代方案
  • 对于可延迟的、保证执行的后台任务,优先使用 WorkManager
  • 对于即时的、简单的后台任务,使用 Kotlin CoroutinesExecutorService
  1. 权限:使用 Foreground Service 必须声明 FOREGROUND_SERVICE 权限。
  2. 电池优化:长时间运行的后台服务会消耗电量,应尽量优化。
七、 Service 与 WorkManager 的选择

特性

Service

WorkManager

任务类型

即时、长时间、用户可见

可延迟、保证执行、非即时

系统优化

较少,易被杀死

高度优化,根据系统条件(充电、空闲)调度

保证执行

不保证(可能被杀死)

保证(即使应用退出或设备重启)

使用复杂度

较高(需处理线程、生命周期)

较低(声明式 API)

推荐场景

音乐播放、实时位置更新

数据同步、日志上传、定期备份

总结: Service 是一个强大的组件,但使用时必须谨慎处理线程和生命周期。对于现代 Android 开发,WorkManagerCoroutines 往往是更优的选择,除非您确实需要 Service 提供的特定功能(如前台通知、长期绑定)。

举报

相关推荐

0 条评论