Android 中 Service 用法和最佳實踐
這篇就講一件事:Service 到底什么時候用,怎么用,怎么不踩坑。
你可以先把 Service 理解成:
“App 沒有頁面展示時,仍然需要在后臺繼續(xù)干活的一個組件”。
就像關(guān)注博主,下次容易找到一樣。
點點關(guān)注一下
常見場景:
- 音樂后臺播放
- 文件上傳下載
- 定位持續(xù)上報
- 藍牙設(shè)備連接與通信
1. 先記住這 3 句話
Service不是線程,它默認(rèn)也跑在主線程。- 耗時任務(wù)不能直接寫在
Service里,不然容易 ANR。 - 真正長期運行的后臺任務(wù),通常要做成前臺服務(wù)(帶通知)。
2. Service 有兩種常用玩法
簡單說:
- 啟動式:我讓你去干活,你自己干完自己停。
- 綁定式:我需要一直和你通信,比如“播放/暫停/下一首”。
2.1 啟動式 Service(Started Service)
啟動方式:
startService(intent)(老方式)- Android 8+ 更常見
startForegroundService(intent)(配合前臺服務(wù))
適合場景:
- 同步數(shù)據(jù)
- 上傳日志
- 一次性后臺處理
核心回調(diào):
onCreate():第一次創(chuàng)建時調(diào)用一次onStartCommand():每次啟動都會進來onDestroy():銷毀時調(diào)用
停止方式:
- 在服務(wù)里
stopSelf() - 外部
stopService(intent)
2.2 綁定式 Service(Bound Service)
綁定方式:bindService(intent, conn, flags)
適合場景:
- 音樂播放器控制
- 下載進度回調(diào)
- 頁面和服務(wù)需要頻繁交互
核心回調(diào):
onCreate()onBind()(返回IBinder)onUnbind()/onRebind()onDestroy()
一句話理解:
頁面拿到 IBinder 后,就能像調(diào)用普通對象一樣調(diào)用服務(wù)方法。
3. 生命周期怎么理解更簡單
啟動式
onCreate() -> onStartCommand()(可能多次)-> onDestroy()
onStartCommand() 返回值怎么選:
START_NOT_STICKY:被系統(tǒng)殺了就算了,不自動重建(一次性任務(wù)常用)START_STICKY:系統(tǒng)盡量拉起來,但Intent可能沒了START_REDELIVER_INTENT:系統(tǒng)重建時,把上次Intent再給你一次
綁定式
onCreate() -> onBind() -> 交互 -> onUnbind() -> onDestroy()
4. 什么是前臺服務(wù)(Foreground Service)
你可以把前臺服務(wù)理解成:
“系統(tǒng)允許你長時間后臺運行,但你必須給用戶一個持續(xù)通知,告訴用戶你在做什么。”
從 Android 8 開始,后臺限制變嚴(yán)格,很多長期任務(wù)都要走前臺服務(wù)。
基本要求:
- 用
startForegroundService(intent)啟動 - 很快調(diào)用
startForeground(notificationId, notification) - 通知必須可見(不能偷偷跑)
Android 14+ 額外注意:
Manifest里寫foregroundServiceType- 類型要和實際任務(wù)對應(yīng)(如
mediaPlayback、location、dataSync) - 相關(guān)權(quán)限要補齊(通知、定位等)
5. 代碼示例(可直接參考)
5.1 啟動式 Service(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 {
// 執(zhí)行耗時任務(wù)
doSync()
} finally {
// 任務(wù)結(jié)束后停止服務(wù)
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: 執(zhí)行同步邏輯
}
}啟動和停止:
ContextCompat.startForegroundService(context, Intent(context, SyncService::class.java)) // 或 context.startService(intent)(低版本或非前臺需求) context.stopService(Intent(context, SyncService::class.java))
5.2 綁定式 Service(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 綁定:
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 配置
<service
android:name=".service.MusicService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />說明:
android:exported="false":只給自己 App 用,默認(rèn)更安全- 只有真的要給其他 App 調(diào)用,才考慮
exported="true",并加權(quán)限保護
7. 到底該不該用 Service
這些情況適合用 Service:
- 用戶能感知到、而且要持續(xù)運行的任務(wù)(音樂、導(dǎo)航、運動)
- App 切后臺后,任務(wù)還要繼續(xù)
這些情況不建議直接用 Service:
- 一次性、可延遲、要求“最終一定執(zhí)行”的任務(wù) -> 優(yōu)先
WorkManager - 頁面內(nèi)短耗時任務(wù) -> 協(xié)程 + 生命周期組件就夠了
8. 常見問題與最佳實踐
- 別在主線程做重活
Service默認(rèn)是主線程,耗時操作請放協(xié)程/線程池。
- 干完就停
- 任務(wù)完成后及時
stopSelf(),別讓服務(wù)白跑。
- 任務(wù)完成后及時
- 前臺服務(wù)別濫用
- 有持續(xù)通知會打擾用戶,必須有明確業(yè)務(wù)價值。
- 注意系統(tǒng)版本差異
- Android 8+ 后臺限制、Android 13+ 通知權(quán)限、Android 14+ 前臺服務(wù)類型都要適配。
- 考慮被系統(tǒng)殺進程
- 按業(yè)務(wù)選好
onStartCommand()返回值,并做好狀態(tài)恢復(fù)。
- 按業(yè)務(wù)選好
- 跨進程通信是進階題
- 真要給其他 App 調(diào)用,通常走 AIDL + 權(quán)限控制。
9. 一句話總結(jié)
Service 適合“沒頁面但要持續(xù)干活”的任務(wù):
只管執(zhí)行用啟動式,需要交互控制用綁定式,長期后臺任務(wù)優(yōu)先做前臺服務(wù)并按系統(tǒng)版本補齊權(quán)限和類型聲明。
到此這篇關(guān)于Android 中 Service 用法的文章就介紹到這了,更多相關(guān)Android Service 用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Studio搜索功能(查找功能)及快捷鍵圖文詳解
這篇文章主要介紹了Android Studio搜索功能(查找功能)及快捷鍵圖文詳解,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12
Android Studio升級4.1.1后各種錯誤和解決方案
這篇文章主要介紹了Android Studio升級4.1.1后各種錯誤和解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
react native中的聊天氣泡及timer封裝成的發(fā)送驗證碼倒計時
這篇文章主要介紹了react native中的聊天氣泡及timer封裝成的發(fā)送驗證碼倒計時的相關(guān)資料,需要的朋友可以參考下2017-08-08
Android使用Dialog風(fēng)格彈出框的Activity
這篇文章主要為大家詳細(xì)介紹了Android使用Dialog風(fēng)格彈出框的Activity,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
Android實現(xiàn)Toast提示框圖文并存的方法
這篇文章主要介紹了Android實現(xiàn)Toast提示框圖文并存的方法,實例分析了Toast提示框的參數(shù)設(shè)置及圖文調(diào)用的相關(guān)技巧,需要的朋友可以參考下2016-01-01
Android listview數(shù)據(jù)顯示及提示信息的實例
這篇文章主要介紹了Android listview數(shù)據(jù)顯示及提示信息的實例的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android 如何獲取手機總內(nèi)存和可用內(nèi)存等信息
這篇文章主要介紹了Android系統(tǒng)檢測程序內(nèi)存占用各種方法,并對內(nèi)存信息的詳細(xì)介紹,需要的朋友可以參考下2016-07-07

