關于 Android Service 的介紹都在這了

概述

Service 是 Android 的四大組件之一,它主要的作用是后臺執行操作,Activity 屬于帶有 UI 界面跟用戶進行交互,而 Service 則沒有 UI 界面,所有的操作都是基于后臺運行完成。并且 Service 跟 Activity 一樣也是可以由其它的應用程序調用啟動的,而且就算用戶切換了應用程序,Service 依舊保持運行。一個組件如果與 Service 進行了綁定( bind ),就可以跟 Service 進行數據的交互,并且也可以跟不同的進程之間進行交互 (IPC)。通常會使用到 Service 的情況有進行網絡請求,音樂的操控,文件的 I/O 操作等。

啟動

Service 通常是通過以下兩種方式進行啟動

startService

當組件(例如 activity)通過調用 startService() 來啟動 Service 的時候。一旦啟動后,Service 就會獨立的在后臺運行,即使調用的組件已經銷毀了,Service 還是可以繼續在后臺運行。一般情況下,只需要進行一次單獨的操作,不需要將操作后的結果返回給調用者的時候,會使用該方式啟動 Service。例如,進行上傳或者下載操作的時候,當操作完成后,Service 應該自行調用 stopService()stopSelf() 來結束運行。

bindService

當組件(例如 activity)通過調用 bindService() 來啟動 Service 的時候。這種方式提供了 client - service 的接口,可以讓調用組件跟 Service 進行發送請求及返回結果的操作,設置可以進行進程間的通信 (IPC)。只要有一個組件對該 Service 進行了綁定,那該 Service 就不會銷毀。并且多個組件可以同時對一個 Service 進行綁定,只有在所有進行了綁定的組件都解綁的時候,Service 才會銷毀

盡管兩種方式是分開討論的,但是并不是互斥的關系,使用 startService 啟動了 Service 后,也是可以進行綁定的。

注意: 雖然 Service 是在后臺運行,但是其實還是在主線程里進行所有的操作的。Service 在啟動時除非單獨進行了定義否則并沒有在單獨的線程或者進程了而都是在主線程里。所以這表示任何能堵塞主線程的操作(例如音樂的播放或者網絡請求)都應該單獨開辟新的線程來進行操作,否則很容易出現 ANR 。

方法

在創建一個 Service 時,必須要去繼承 Service,并且要重寫父類一些主要的方法來實現功能。以下是主要方法的介紹

onStartCommand()

系統會調用這個函數當某個組件(例如 activity,fragment)通過調用 startService() 啟動 Service 時。在該方法被調用后,Service 就會被啟動并獨立的在后臺運行。如果重寫了該方法,開發者需要在 Service 執行完操作后自行的調用 stopSelf()stopService(),來結束 Service。如果只是會通過綁定的方式 (bind) 的方式來啟動 Service 則不需要重寫該方法

onBind()

系統會調用這個函數當某個組件(例如 activity, fragment)通過調用 bindService()綁定的方式來啟動 Service 的時候。在實現這個函數的時候,必須要返回一個 IBinder 的繼承類,來與 Service 進行通信。這個函數是默認必須要重寫的,但是如果不想通過綁定的方式來啟動 Service,則可以直接返回 null

onCreate()

系統會調用此方法在第一次啟動 Service 的時候,用于初始化一些一次性的變量。如果 Service 已經啟動了,則此方法就不會再別調用

onDestroy()

系統在 Service 已經不需要準備被銷毀的時候會調用此方法。Service 中如有用到 threadlistenersreceivers 等的時候,應該將這些的清理方法寫在此方法內

以上就是實現一個 Service 中要實現的一些方法。

如果某個組件是通過調用 startService() 的方式來啟動了 Service,那這個 Service 就會一直在后臺運行直到 Service 內部調用 stopSelf() 或某個組件調用 stopService() 來結束該 Service

如果某個組件是通過調用 bindService() 的方式來啟動了 Service,那這個 Service 就會一直在后臺運行直到該組件與其解綁。Service 在沒有任何組件綁定的時候,系統會將其銷毀

下面的環節,將介紹如果通過上面講述的兩種方式來創建 Service

在 Manifest 里聲明 Service

類似于 Activity,所有的 Service 都要在 Manifest 里面進行聲明,如下:

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

查看 <service> 標簽的官方文檔來獲取更多信息

通過在 <service> 標簽里將 android:exported 設置為 false。可以防止其他的程序來啟動你的 Service

通過 started 方式來啟動 Service

組件(例如 activity, fragment) 通過調用 startService() 方法,系統隨之調用 onStartCommand() 方法來實現 started 方式啟動 Service。

當 Service 以該形式啟動后,Service 的整個生命周期是完全獨立的,即便啟動 Service 的組件已經被銷毀了,Service 還是可以在后臺無限的運行的。但開發者應該在 Service 中的操作執行完成后,調用 stopSelf() 或其它組件調用 stopService() 的方式來結束該 Service。

程序組件(例如 activity) 可以通過傳遞一個 IntentstartService(),來實現組件與 Service 之前的數據傳遞。Service 是通過系統調用的 onStartCommand() 方法接受傳遞的Intent ,完成整個數據傳遞過程。

注意: Service 本身默認是運行在主線程里的,所以如果在 Service 要進行一些會堵塞線程的操作,一定要將這些操作放在一個新的線程里。

Android 的框架提供了 IntentService 來滿足后臺運行異步線程的需求。

IntentService

IntentService 是 Service 的子類,并且所有的請求操作都是在異步線程里。如果不需要 Service 來同時處理多個請求的話,IntentService 將會是最佳的選擇。只需要繼承并重寫 IntentService 中的 onHandleIntent() 方法,就可以對接受到的 Intent 做后臺的異步線程操作了。

IntentService 提供了如下的幾個功能:

  • 會創建一個異步線程來處理所有從程序主線程發送到 onStartCommand() 的 intents 。
  • 創建了一個隊列池來保證每次只有一個 Intent 傳遞到 onHandleIntent() 方法中,避免了多線程問題。
  • 提供默認 onBind() 方法的實現,返回 null
  • 提供默認 onStartCommand() 方法的實現,將收到的 intent 放到隊列池中,然后再交由 onHandleIntent() 做處理

所有開發者只需要實現 onHandleIntent() 關注于 Service 要進行的操作即可。關于更多的 IntentService 實用技巧,請查看此文章

如果開發者需要重寫其他的一些方法,例如 onCreate(), onStartCommand(), 和 onDestroy(),請保證調用父類的實現,這樣可以保證 IntentService 能夠正確的來處理線程的生命周期。例如:

@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);
}
系統回收資源問題

當系統內存不足的時候,系統會強制回收一些 Activity 和 Service 來獲取更多的資源給那些用戶正在交互的程序或頁面。這就要求 Service 能夠自動重啟當資源充足的時候。這個功能是通過 onStartCommand() 的返回值來實現的

START_NOT_STICKY

當系統因回收資源而銷毀了 Service,當資源再次充足時不自動啟動 Service。除非還有為處理的 Intent 準備發送。當你的程序可以很容易的重新開啟未完成的操作時,這是最安全的避免 Service 在不必要的情況下啟動的選項。

START_STICKY

當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service,并且再次調用 onStartCommand() 方法,但是不會傳遞最后一次的 Intent,相反系統在回調onStartCommand() 的時候會傳一個空 Intent 。除非還有為處理的 Intent 準備發送

START_REDELIVER_INTENT

當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service,并且再次調用 onStartCommand() 方法,并會把最后一次 Intent 再次傳遞給,onStartCommand(),相應的在隊列里的 Intent 也會按次序一次傳遞。此模式適用于下載等服務。

Start Service

該方式允許多個組件同時對相同的 Service 進行 startService 操作,但是如果只要有其中有一個組件調用了 stopSelf()stopService(), 該 Service 就會被銷毀

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

當有多個組件進行了 startService 操作時,不應該直接的去調用 stopSelf()stopService() 來結束 Service, 因為這會對其他已經發起請求的操作產生影響,故在 onStartCommand() 方法中會接受一個 startId, 然后在結束 Service 時,調用 stopService(int) 方法來只是結束一個特定的請求,從而達到保護其他請求不受影響的目的

通過 Bind 方式啟動 Service

當應用程序中的 activity 或其它組件需要與服務進行交互,或者應用程序的某些功能需要暴露給其它應用程序時,你應該創建一個 Bind 服務,并通過進程間通信(IPC)來完成。

Service 只在為綁定的應用程序組件工作時才會存活,因此,只要沒有組件綁定到服務,系統就會自動銷毀服務

獲取 IBinder 實例
擴展 Binder 類

如果服務是你的應用程序所私有的,并且與客戶端運行于同一個進程中(通常都是如此),你應該通過擴展 Binder 類來創建你的接口,并從 onBind() 返回一個它的實例。客戶端接收該 Binder 對象并用它來直接訪問 Binder 甚至 Service中可用的公共方法。

如果你的服務只是為你自己的應用程序執行一些后臺工作,那這就是首選的技術方案。不用這種方式來創建接口的理由只有一個,就是服務要被其它應用程序使用或者要跨多個進程使用。

使用 Messenger

如果你需要接口跨越多個進程進行工作,可以通過 Messenger 來為服務創建接口。在這種方式下,服務定義一個響應各類消息對象 MessageHandler。此 HandlerMessenger 與客戶端共享同一個 IBinder 的基礎,它使得客戶端可以用消息對象 Message向服務發送指令。此外,客戶端還可以定義自己的 Message ,以便服務能夠往回發送消息。

這是執行進程間通信(IPC)最為簡便的方式,因為 Messenger 會把所有的請求放入一個獨立進程中的隊列,這樣你就不一定非要把服務設計為線程安全的模式了。

使用 AIDL

絕大多數應用程序都不應該用 AIDL 來創建 bind 服務,因為這可能需要多線程處理能力并且會讓代碼變得更為復雜。 因此,AIDL 對絕大多數應用程序都不適用,并且本文也不會討論如何在服務中使用它的內容。如果你確信需要直接使用 AIDL,那請參閱 AIDL 文檔。

擴展 Binder 類

如果你的服務只用于本地應用程序并且不需要跨進程工作,那你只要實現自己的 Binder 類即可,這樣你的客戶端就能直接訪問服務中的公共方法了。

注意:僅當客戶端和服務位于同一個應用程序和進程中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應用需要把一個 activity 綁定到它自己的后臺音樂播放服務上,采用這種方式就會很不錯。

以下是設置步驟:

  1. 在你的服務中,創建一個 Binder 的實例,其中實現以下三者之一:
    • 包含了可供客戶端調用的公共方法
    • 返回當前 Service 實例,其中包含了可供客戶端調用的公共方法
    • 或者,返回內含 Service 類的其它類的一個實例,Service 中包含了可供客戶端調用的公共方法
  2. 從回調方法 onBind() 中返回 Binder 的該實例
  3. 在客戶端中,在回調方法 onServiceConnected() 中接收 Binder 并用所提供的方法對綁定的服務進行調用

注意:
服務和客戶端之所以必須位于同一個應用程序中,是為了讓客戶端能夠正確轉換(cast)返回的對象并調用對象的 API。 服務和客戶端也必須位于同一個進程中,因為這種方式不能執行任何跨進程的序列化(marshalling)操作。

具體的實用案例請查看在ApiDemos 中的 LocalService.java 類和 LocalServiceActivities.java

使用 Messenger

以下概括了Messenger的使用方法:

  • 服務實現一個 Handler ,用于客戶端每次調用時接收回調
  • Handler 用于創建一個 Messenger 對象(它是一個對 Handler 的引用)
  • Messenger 對象創建一個 IBinder ,服務在 onBind() 中把它返回給客戶端
  • 客戶端用 IBinderMessenger(引用服務的 Handler)實例化,客戶端用它向服務發送消息對象 Message
  • 服務接收 Handler 中的每個消息 Message ——確切的說,是在 handleMessage() 方法中接收

MessengerService.java(服務)和 MessengerServiceActivities.java(客戶端)例程中,可以看到如何關于 Messenger 的實用例子。

Service 的生命周期

service 生命周期圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 上篇我們講解了Android中的5中等級的進程,分別是:前臺進程、可見進程、服務進程、后臺進程、空進程。系統會按照...
    徐愛卿閱讀 3,876評論 6 33
  • 前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文鏈接,demo鏈接 Serv...
    PassersHowe閱讀 1,438評論 0 5
  • 這世界上有一千種等待,最好的那一種,叫做來日可期,我愿意站在這里,從這一秒開始倒數,等待多年后的相遇。
    guoguoguoguoguo閱讀 176評論 0 0
  • 一直就喜歡看天,藍天,白云。好像那里才有自由,自由自在,無憂無慮!真好,想想都美!!所以我的相冊里最多的風景就是—...
    路驍閱讀 274評論 0 1
  • 那年月都還年青,出點力其實并不算個啥。如果都能象副班長那樣有一技之長,只圖的有個用處,有個安身之處,不愁吃穿用度,...
    焦作太極徐閱讀 216評論 0 0