在 Android 開發中,Service 是處理後台任務的核心組件之一。然而,隨着系統對後台限制日益嚴格(尤其是 Android 8.+),很多開發者對 Service 的使用感到困惑:它到底運行在主線程嗎?startService 和 bindService 有何區別?如何避免被系統殺死?本文將帶你徹底搞懂 Service,並提供可落地的保活與替代方案。

一、什麼是 Service?一句話説清本質

Service 是一個沒有用户界面、可在後台執行長時間運行操作的組件。

⚠️ 必須澄清的 3 個常見誤解

誤解

正確理解

Service 運行在子線程

❌ 默認仍在主線程!耗時操作必須手動開線程

Service = 後台進程

❌ 它只是組件,進程由系統調度,可能被殺

startService 後 App 退出,Service 就停了

❌ 只要沒調 stopSelf()stopService(),Service 會繼續運行(但受後台限制)

二、Service 的兩種核心類型

1. 啓動型 Service(Started Service)

  • 通過 startService() 啓動
  • 用於執行一次性後台任務(如下載文件、上傳日誌)
  • 生命週期獨立於啓動者
  • 必須主動調用 stopSelf() 或外部調用 stopService() 停止

2. 綁定型 Service(Bound Service)

  • 通過 bindService() 綁定
  • 用於客户端-服務端通信(如音樂播放器控制)
  • 生命週期依賴於綁定的客户端數量
  • 所有客户端 unbindService() 後自動銷燬

💡 實際項目中,常混合使用(先 start 再 bind),實現“既長期運行又能通信”。


三、Service 生命週期詳解(附流程圖)

核心回調方法(6 個)

class MyService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d("Service", "onCreate: 服務創建")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("Service", "onStartCommand: 接收到啓動命令")
        // 執行後台任務(注意:這裏仍在主線程!)
        doBackgroundWork()
        // 返回值決定被殺後是否重建
        return START_STICKY // 被殺後嘗試重建(不傳 intent)
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.d("Service", "onBind: 客户端綁定")
        return MyBinder()
    }

    override fun onRebind(intent: Intent?) {
        Log.d("Service", "onRebind: 重新綁定")
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.d("Service", "onUnbind: 所有客户端解綁")
        return true // 允許 onRebind
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("Service", "onDestroy: 服務銷燬")
    }

    private fun doBackgroundWork() {
        // ✅ 正確做法:開啓子線程
        Thread {
            // 模擬耗時操作
            Thread.sleep(5000)
            // 注意:不要在這裏直接更新 UI!
        }.start()
    }

    inner class MyBinder : Binder() {
        fun getService(): MyService = this@MyService
    }
}

生命週期流程圖

startService() → onCreate() → onStartCommand() → [運行中] → stopService()/stopSelf() → onDestroy()

bindService() → onCreate() → onBind() → [綁定中] → unbindService() → onUnbind() → onDestroy()

🔔 關鍵點onStartCommand() 返回值決定行為:

  • START_STICKY:被殺後重建(intent 為 null)
  • START_NOT_STICKY:被殺後不重建
  • START_REDELIVER_INTENT:被殺後重建並重傳 intent(適合重要任務)

四、前台服務(Foreground Service)—— 保活的關鍵!

從 Android 8.0(API 26)開始,普通後台 Service 會在幾分鐘內被系統限制。前台服務是官方推薦的長期運行方案

✅ 實現步驟

1. 聲明權限(AndroidManifest.xml)

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

2. 創建通知渠道(Android 8.0+ 必需)

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            "SERVICE_CHANNEL",
            "Service Channel",
            NotificationManager.IMPORTANCE_LOW
        )
        getSystemService(NotificationManager::class.java)
            .createNotificationChannel(channel)
    }
}

3. 啓動前台服務

override fun onCreate() {
    super.onCreate()
    createNotificationChannel()
    
    val notification = NotificationCompat.Builder(this, "SERVICE_CHANNEL")
        .setContentTitle("我的服務正在運行")
        .setContentText("點擊返回應用")
        .setSmallIcon(R.drawable.ic_notification)
        .build()

    // 關鍵!將服務轉為前台
    startForeground(1, notification)
}

📌 注意startForeground() 必須在 onCreate()onStartCommand()5秒內調用,否則 ANR!

五、Service 保活?現實很骨感!

殘酷真相:在 Android 8.0+,沒有任何 100% 保活方案。廠商 ROM(如華為、小米)會主動殺死後台進程。

可嘗試的“軟保活”策略(效果有限)

  1. 雙進程守護(AIDL + 兩個 Service 互相監聽)
    → 系統優化後基本失效
  2. 提高進程優先級
// 在 Service 中啓動一個前台 Activity(不推薦,會被視為惡意行為)
  1. 利用 JobScheduler / WorkManager 替代長期後台任務
    這才是現代 Android 的正確做法!

六、替代方案:用 WorkManager 處理後台任務(推薦!)

Google 官方推薦使用 WorkManager 處理可延遲、需保證執行的後台任務,它能智能適配不同 Android 版本(內部使用 JobScheduler / AlarmManager / Broadcast)。

示例:每天凌晨同步數據

val syncWorker = PeriodicWorkRequestBuilder<SyncWorker>(
    24, TimeUnit.HOURS,
    1, TimeUnit.HOURS // 允許 1 小時浮動
).build()

WorkManager.getInstance(this).enqueueUniquePeriodicWork(
    "DailySync",
    ExistingPeriodicWorkPolicy.KEEP,
    syncWorker
)

// Worker 實現
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 在後台線程執行
        performSync()
        return Result.success()
    }
}

優勢:省電、兼容性好、系統優化友好
不適用:需要實時持續運行的任務(如音樂播放、定位追蹤)

七、總結:何時用 Service?

場景

推薦方案

播放音樂、導航等持續運行任務

前台 Service

下載大文件、上傳日誌等一次性任務

普通 Service + 子線程(或直接用 Coroutine

定時同步、備份等可延遲任務

WorkManager

需要與 Activity 雙向通信

Bound Service + AIDL/Messenger

不要為了“保活”而濫用 Service。尊重用户電池和系統資源,才是長久之道。