Android Service最全面的解析

2021-02-19 郭霖

本篇文章再次來自 劉明淵 ,話說劉明淵已經是我公眾號的老熟人了,這是第三次發表他投稿的文章。前兩篇關於Intent的譯文都廣受大家好評,而本篇對於Service的譯文同樣精彩。其實像這種官方文檔翻譯類文章的投稿我都是非常歡迎的,因為官方文檔的質量首先是有保障的,再者解決了一些無法直接看英文文檔或者翻不了牆的朋友的閱讀障礙,希望有更多的朋友可以來多多投稿。

劉明淵 的博客地址:http://blog.csdn.net/vanpersie_9987

Service是Android中一個類,它是Android四大組件之一,使用Service可以在後臺執行長時間的操作( perform long-running operations in the background ),Service並不與用戶產生UI交互。其他的應用組件可以啟動Service,即便用戶切換了其他應用,啟動的Service仍可在後臺運行。一個組件可以與Service綁定並與之交互,甚至是跨進程通信(IPC)。例如,一個Service可以在後臺執行網絡請求、播放音樂、執行文件讀寫操作或者與 content provider交互等。

本文將介紹Services的定義、創建、啟動、綁定、前臺Service等相關內容。


Service有兩種啟動形式:

Started:其他組件調用startService()方法啟動一個Service。一旦啟動,Service將一直運行在後臺(run in the background indefinitely)即便啟動Service的組件已被destroy。通常,一個被start的Service會在後臺執行單獨的操作,也並不給啟動它的組件返回結果。比如說,一個start的Service執行在後臺下載或上傳一個文件的操作,完成之後,Service應自己停止。

Bound:其他組件調用bindService()方法綁定一個Service。通過綁定方式啟動的Service是一個client-server結構,該Service可以與綁定它的組件進行交互。一個bound service僅在有組件與其綁定時才會運行(A bound service runs only as long as another application component is bound to it),多個組件可與一個service綁定,service不再與任何組件綁定時,該service會被destroy。

當然,service也可以同時在上述兩種方式下運行。這涉及到Service中兩個回調方法的執行:onStartCommand()(通過start方式啟動一個service時回調的方法)、onBind()(通過bind方式啟動一個service回調的方法)。


無論通過哪種方式啟動service(start、bind、start&bind),任何組件(甚至其他應用的組件)都可以使用service。並通過Intent傳遞參數。當然,您也可以將Service在manifest文件中配置成私有的,不允許其他應用訪問。

請注意:Service運行在主線程中(A service runs in the main thread of its hosting process),Service並不是一個新的線程,也不是新的進程。也就是說,若您需要在Service中執行較為耗時的操作(如播放音樂、執行網絡請求等),需要在Service中創建一個新的線程。這可以防止ANR的發生,同時主線程可以執行正常的UI操作。


使用Service還是使用Thread?

Service是一個運行在後臺的組件,並不與用戶交互。您僅在需要的時候創建Service( create a service only if that is what you need)。

當用戶正在與UI交互時,需要執行一些主線程無法完成的工作,應當創建一個線程。例如當activity正在運行時,需要播放音樂,此時需要在Activity的onCreate()中創建線程並在onStart()中開啟。最後在onStop()中停止。您也可以考慮使用AsyncTask 或 HandlerThread來替代Thread創建線程。

為了創建Service,需要繼承Service類。並重寫它的回調方法,這些回調方法反應了Service的生命周期,並提供了綁定Service的機制。最重要的Service的生命周期回調方法如下所示:

onStartCommand()當其他組件調用startService()方法請求啟動Service時,該方法被回調。一旦Service啟動,它會在後臺獨立運行。當Service執行完以後,需調用stopSelf() 或 stopService()方法停止Service。(若您只希望bind Service,則無需調用這些方法)

onBind()當其他組件調用bindService()方法請求綁定Service時,該方法被回調。該方法返回一個IBinder接口,該接口是Service與綁定的組件進行交互的橋梁。若Service未綁定其他組件,該方法應返回null。

onCreate():當Service第一次創建時,回調該方法。該方法只被回調一次,並在onStartCommand() 或 onBind()方法被回調之前執行。若Service處於運行狀態,該方法不會回調。

onDestroy()當Service被銷毀時回調,在該方法中應清除一些佔用的資源,如停止線程、接觸綁定註冊的監聽器或broadcast receiver 等。該方法是Service中的最後一個回調。

如果某個組件通過調用startService()啟動了Service(系統會回調onStartCommand()方法),那麼直到在Service中手動調用stopSelf()方法、或在其他組件中手動調用stopService()方法,該Service才會停止。

如果某個組件通過調用bindService()綁定了Service(系統不會回調onStartCommand()方法),只要該組件與Service處於綁定狀態,Service就會一直運行,當Service不再與組件綁定時,該Service將被destroy。

當系統內存低時,系統將強制停止Service的運行;若Service綁定了正在與用戶交互的activity,那麼該Service將不大可能被系統kill( less likely to be killed)。如果創建的是前臺Service,那麼該Service幾乎不會被kill(almost never be killed)。否則,當創建了一個長時間在後臺運行的Service後,系統會降低該Service在後臺任務棧中的級別——這意味著它容易被kill(lower its position in the list of background tasks over time and the service will become highly susceptible to killing),所以在開發Service時,需要使Service變得容易被restart,因為一旦Service被kill,再restart它需要其資源可用時才行(restarts it as soon as resources become available again ),當然這也取決於onStartCommand()方法返回的值,這將在後續介紹。

在manifest文件中註冊service的方式如下:

<manifest ... >  ...  <application ... >      <service android:name=".ExampleService" />      ...  </application>
</manifest>

除此之外,在<service>標籤中還可以配置其他屬性,比如,需要啟動該service所需的權限、該service應運行在哪個進程中 等( permissions required to start the service and the process in which the service should run)。android:name屬性是唯一不可預設的,它指定了Service的全限定類名。一旦發布了應用,該類名將不可更改。

請注意:為了保證應用的安全,請使用顯式Intent啟動或綁定一個Service,請不要在<service>標籤中配置intent-filter。


若不確定該啟動哪個Service,那麼可以在<service>中配置intent-filter,並在Intent中排除該Service(supply intent filters for your services and exclude the component name from the Intent),但必須調用Intent的setPackage()方法,來為啟動的service消除歧義(provides sufficient disambiguation for the target service)。

註:setPackage()方法傳入一個String參數,代表一個包名。該方法表示該Intent對象只能在傳入的這個包名下尋找符合條件的組件,若傳入null,則表示可以在任意包下尋找。

android:exported屬性設為false,表示不允許其他應用程式啟動本應用的組件,即便是顯式Intent也不行(even when using an explicit intent)。這可以防止其他應用程式啟動您的service組件。

在大多數情況下,start Service並不會同時處理多個請求(don't need to handle multiple requests simultaneously),因為處理多線程較為危險(a dangerous multi-threading scenario),所以繼承IntentService類帶創建Service是個不錯選擇。

使用IntentService的要點如下:

綜上所述,您只需重寫onHandleIntent()方法即可,當然,還需要創建一個構造方法,示例如下:



如果您還希望在IntentService 的繼承類中重寫其他生命周期方法,如onCreate()、onStartCommand() 或 onDestroy(),那麼請先調用各自的父類方法以保證子線程能夠正常啟動。

比如,要實現onStartCommand()方法,需返回其父類方法:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
   Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
   return super.onStartCommand(intent,flags,startId);}

onHandleIntent()外,onBind()方法也無需調用其父類方法。

如果您需要在Service中執行多線程而不是處理一個請求隊列(perform multi-threading instead of processing start requests through a work queue),那麼需要繼承Service類,分別處理每個Intent。

在Service中執行操作時,處理每個請求都需要開啟一個線程,並且同一時刻一個線程只能處理一個請求( for each start request, it uses a worker thread to perform the job and processes only one request at a time)。



注意到onStartCommand()返回一個整形變量,該變量必須是下列常量之一:

若需要啟動Service,見下面所示:

Intent intent = new Intent(this, HelloService.class);startService(intent);

startService(intent)方法將立即返回,並回調onStartCommand()(請不要手動調用該方法),若該Service未處於運行狀態,系統將首先回調onCreate(),接著再回調onStartCommand()。若您希望Service可以返回結果,那麼需要通過調用getBroadcast返回的PendingIntent啟動Service(將PendingIntent包裝為Intent),service可使用broadcast 傳遞結果。

多個啟動Service的請求可能導致onStartCommand()多次調用,但只需調用stopSelf() 、 stopService()這兩個方法之一,就可停止該服務。

一個啟動的Service必須管理自己的生命周期。系統不會主動stop或destroy一個運行的Service,除非系統內存緊張,否則,執行完onStartCommand()方法後,Service依然運行。停止Service必須手動調用stopSelf()(在Service中)或調用stopService()(在啟動組件中)。

一旦調用了上述兩種方法之一,系統會儘快destroy該Service(as soon as possible)。

若系統正在處理多個調用onStartCommand()請求,那麼在啟動一個請求時,您不應當在此時停止該Service(you shouldn’t stop the service when you’re done processing a start request)。為了避免這個問題,您可以調用stopSelf(int)方法,以確保請求停止的Service時最新的啟動請求( your request to stop the service is always based on the most recent start request)。這就是說,當調用stopSelf(int)方法時,傳入的ID代表啟動請求(該ID會傳遞至onStartCommand()),該ID與請求停止的ID一致。則如果在調用stopSelf(int)之前,Service收到一個新的Start請求,ID將無法匹配,Service並不會停止。

為了節省內存和電量,當Service完成其工作後將其stop很有必要。如有必要,可以在其他組件中調用stopService()方法,即便Service處於綁定狀態,只要它回調過onStartCommand(),也應當主動停止該Service。

通過其他組件調用bindService()方法可以綁定一個Service以保持長連接,這時一般不允許其他組件調用startService()啟動Service。

當其他組件需要與Service交互或者需要跨進程通信時,可以創建一個bound Service。

為創建一個bound Service,必須重寫`onBind()`回調,該方法返回一個IBinder接口。該接口時組件與Service通信的橋梁。組件調用bindService()與Service綁定,該組件可獲取IBinder接口,一旦獲取該接口,就可以調用Service中的方法。一旦沒有組件與Service綁定,系統將destroy它,您不必手動停止它。

為創建一個bound Service,必須定義一個接口 ,該接口指定組件與Service如何通信。定義的接口在組件與Service之間,且必須實現IBinder接口。這正是onBind()的返回值。一旦組件接收了IBinder,組件與Service便可以開始通信。

多個組件可同時與Service綁定,當組件與Service交互結束後,可調用unbindService()方法解綁。bound Service比start Service要複雜,故我將在後續單獨翻譯。

運行中的Service可以通過Toast Notifications 或 Status Bar Notifications 向用戶發送通知。Toast是一個可以短時間彈出的提醒框。Status Bar是頂部狀態欄中出現的太有圖標的信息,用戶可以通過下拉狀態欄獲得具體信息並執行某些操作(如啟動Activity)。

通常,Status Bar用於通知某些操作已經完成,如下載文件完成。當用戶下拉狀態欄後,點擊該通知,可獲取詳細內容,如查看該下載的文件。

前臺Service用於動態通知消息,如天氣預報。該Service不易被kill。前臺Service必須提供status bar,只有前臺Service被destroy後,status bar才能消失。

舉例來說,一個播放音樂的Service必須是前臺Service,只有這樣用戶才能確知其運行狀態。為前臺Service提供的status bar可以顯示當前音樂的播放狀態,並可以啟動播放音樂的Activity。

調用startForeground()可以啟動前臺Service。該方法接收兩個參數,參數一是一個int型變量,用戶指定該通知的唯一性標識,而參數而是一個Notification用於配置status bar,示例如下:


調用stopForeground()來移除(remove)前臺Service。該方法需傳入一個boolean型變量,表示是否也一併清除status bar上的notification(indicating whether to remove the status bar notification as well)。該方法並不停止Service,如果停止正在前臺運行的Service,那麼notification 也一併被清除。

從Service的啟動到銷毀,有兩種路徑:

A started service:需手動停止 

A bound service:可自動停止

如下圖所示 :


這兩條路徑並不是毫不相干的:當調用startService()啟動一個Service後,您仍可以bind該Service。比如,當播放音樂時,需調用startService()啟動指定播放的音樂,當需要獲取該音樂的播放進度時,有需要調用bindService(),在這種情況下,知道Service被unbind ,調用stopService() 或stopSelf()都不能停止該Service。


這些生命周期方法在使用時無需調用各自的父類方法。


在兩條生命周期路徑中,都包含了兩個嵌套的生命周期:

無論是startService() 還是 bindService()啟動Service,onCreate() 和 onDestroy()均會被回調。

請注意:針對startService(),由於Service中沒有類似onStop()的回調,所以在調用stopSelf() 或 stopService()後,只有onDestroy()被回調標誌著Service已停止。

如果你有好的技術文章想和大家分享,歡迎向我的公眾號投稿,投稿具體細節請在公眾號主頁點擊「投稿」菜單查看。

歡迎長按下圖 -> 識別圖中二維碼或者掃一掃關注我的公眾號:


相關焦點

  • Android-HIDL實例解析
    -9.0-sdk$ tree hardware/interfaces/naruto/hardware/interfaces/naruto/└── 1.0 ├── Android.bp ├── default │   ├── Android.bp │   ├── android.hardware.naruto@1.0-service.rc
  • Android WebView 與 JS 的交互方式最全面匯總
    前言閱讀本文前請先閱讀:Android開發:最全面、最易懂的Webview詳解目錄
  • 可能是目前最全的《Android面試題及解析》(379頁)
    趁著這段時間,小夥伴們可以參考這份可能是市面上最全面的安卓面試題解析大全!從基礎到架構進階,包含了騰訊、百度、小米、阿里、樂視、美團、58、獵豹、360、新浪、搜狐等一線網際網路公司面試被問到的題目,涵蓋了初中高級安卓技術點。文章中所列主要為大綱部分,詳細內容可以在文末自行獲取哈!
  • Android 軟鍵盤的全面解析,讓你不再怕控制項被遮蓋
    本文將從以下幾個方面進行介紹:InputMethodService的源碼解析,從源碼解讀中告訴你為什麼軟鍵盤彈出的是一個DialogAndroid軟鍵盤顯示時,設置windowSoftInputMode的作用EditText設置imeOptions屬性對軟鍵盤的影響軟鍵盤上面的按鍵監聽橫屏狀態下,不希望軟鍵盤顯示全屏怎麼處理
  • 【轉載】Android 軟鍵盤的全面解析,讓你不再怕控制項被遮蓋
    本文將從以下幾個方面進行介紹:InputMethodService的源碼解析,從源碼解讀中告訴你為什麼軟鍵盤彈出的是一個DialogAndroid軟鍵盤顯示時,設置windowSoftInputMode的作用EditText設置imeOptions屬性對軟鍵盤的影響軟鍵盤上面的按鍵監聽橫屏狀態下,不希望軟鍵盤顯示全屏怎麼處理
  • 【android開發】Android binder學習一:主要概念
    Binder基於Client-Server通信模式,傳輸過程只需一次拷貝,為發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。binder_become_context_manager通知binder driver使SM為context manager。binder_loop是一個死循環,裡面不停的讀binder是否有數據,如果有數據,則解析,對於BR_TRANSACTION,會調用svcmgr_handler來處理。SM維護了一個svclist來存儲service的信息。
  • 助攻面試:圖解Android Binder機制
    最上層是application應用層,第二層是Framework層,第三層是native層。由下圖可知幾點:1、Android中的應用層和系統服務層不在同一個進程,系統服務在單獨的進程中。2、Android中不同應用屬於不同的進程中。
  • Android開發必備的「80」個開源庫
    //www.androiddevtools.cn/Android 開源項目分類匯總 —— 史上最全的Android開源項目匯總https://github.com/Trinea/android-open-projectAndroid 資源庫列表 —— 超級棒的安卓資源庫列表http://app.memect.com/doc/android.html
  • android基礎入門
    最後我們會做一個簡單遊戲,不過麻雀雖小、五臟俱全,這個項目從需求分析到最後上線和嵌入廣告的過程都會完整的展現,讓大家以最快的速度掌握Android的開發流程。第一天、android簡介和開發環境的搭建:android的簡介android開發環境搭建android工程的目錄介紹
  • Android PMS處理APK的複製
    frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java void installStage(String packageName, File stagedDir, String stagedCid,
  • 深入解析Kubernetes service 概念
    深入解析Kubernetes service 概念Kubernetes在Kubernetes平臺上,Pod是有生命周期,為了可以給客戶端一個固定的訪問端點,因此需要在客戶端和Pod之間添加一個中間層,這個中間層稱之為ServiceService是什麼?
  • Android深入四大組件(二)Service的啟動過程
    frameworks/base/core/java/android/app/ActivityThread.javaframeworks/base/core/java/android/app/ActivityThread.java
  • Android解析WindowManager(三)Window的添加過程
    本篇主要會講解Window的操作的WindowManager處理部分,至於WMS處理部分會在後續的解析WMS系列文章中進行講解。frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
  • Android Backup 文件解析
    第二種方法則需要了解一下文件格式(其實也沒多少- -).我去github上找了一下,backup 在 * [這裡](https://github.com/android/platform_frameworks_base/blob/master/services/backup/java/com/android/server/backup/BackupManagerService.java),另外還有一個
  • android app被殺原因專題及常見問題 - CSDN
    分析長按HOME鍵清理App最終會執行到ActivityManagerService.cleanUpRemovedTaskLocked方法中,ActivityManagerService類在文件"frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java"中,
  • Android大廠面試題錦集附答案(BAT TMD JD 小米)
    來源:https://www.jianshu.com/p/cf5092fa2694這一年我兜兜轉轉從android到java又回到android,校招面了很多大廠,阿里、京東、小米、頭條、知乎、騰訊、有贊,也收穫了幾個offer。感謝大家的關注,為了回饋大家,一篇最完全的android面經誕生了。
  • 獨立系統且非Android 阿里雲OS全面解析
    獨立系統且非Android 阿里雲OS全面解析    阿里雲OS其實是作業系統與雲服務相併的研發理念,簡單的來說阿里雲OS阿里雲OS系統技術體系架構    和Android一樣,阿里雲OS其實也是個分層的架構(上圖為阿里雲OS、下圖為Android),對於Android來說,從上往下看分別是應用程式層、應用程式框架層、系統運行庫層,最下面是標準的
  • Android系統啟動流程(三)解析SyetemServer進程啟動過程
    frameworks/base/core/java/com/android/internal/os/RuntimeInit.javaframeworks/base/core/java/com/android/internal/os/RuntimeInit.java
  • 【學習經驗】android開發的學習路線
    第二階段:Java Web開發1.Java解析XML文件DOM4J。2.MySql資料庫的應用、多表連接查詢的應用。3.Jsp和Servlet應用。4.Http協議解析。5.Tomcat伺服器的應用配置。6.WebService服務配置應用。
  • android藍牙框架專題及常見問題 - CSDN
    代碼來源於Android P,本文相關代碼:client:frameworks/base/core/java/android/bluetooth/*system/bt/binder/android/bluetooth/**.aidlservie:framework/base/services/core/java/com/android/server