1. 程式人生 > >解讀Android之Service(1)基礎知識

解讀Android之Service(1)基礎知識

本文翻譯自Android官方文件

一個Service是一個長期可以在後臺執行(當然不需要提供UI)的應用元件。其它元件可以啟動service,即使切換到另一個應用,該service仍然可以在後臺執行。另外,其它元件可以繫結一個service進行互動,甚至可以進行程序間通訊(interprocess communication,IPC)。正如activity一樣,service也必須在AndroidManifest.xml中進行註冊。

  • service不是單獨的程序,除非特別指定,否則它在應用程式的程序中。
  • service不是執行緒,但是它經常開啟一個執行緒處理任務。

一個service本質上可以分為兩種:

  • Started 
    即啟動方式的service。當一個元件通過startService()方法啟動service時,該service為”started”。一旦service啟動,則無論啟動它的元件是否被銷燬,該service都能獨立執行。通常,這種方式的service執行一些簡單的操作,且不帶有返回值。例如,它能夠下載/上傳檔案,當操作完成時,該service需要停止。
  • Bound 
    即繫結方式的service。當元件通過bindService()方法繫結一個service時,該service為繫結service。繫結service允許元件和繫結service進行互動,例如傳送請求,獲得結果,甚至跨程序通訊(IPC)。一個繫結service一旦被繫結,該service就會處於執行狀態。多個元件可以繫結一個service,但只有所有的元件都解綁時,該service才銷燬。

雖然這裡是分開介紹這兩種service,但是這兩種方式也可以結合在一起使用。通常的做法是先啟動一個service,然後在需要該service時再繫結。

service同activity一樣可以在同一應用程式中或其它應用程式中被啟動,繫結,或兩者同時操作。當然,service也可以設定為私有的,詳細在下面介紹。

注意: 
service不會建立新的執行緒,不會執行在其它程序中(除非開發人員指定),它執行在主執行緒中。這就意味著,service不能執行長時間的操作(佔用CPU,阻塞操作),若service需要長時間執行,我們可以在一個子執行緒中執行service,否則會造成應用程式無反應(Application Not Responding,ANR)錯誤。

使用service還是執行緒?

service是一個能夠執行在後臺的簡單元件(即使使用者不和我們的程式互動)。如果你需要執行的任務不在主執行緒並且只在與使用者互動的過程中才執行,此時你應該使用執行緒,而不用service(當然service也可以實現)。請記住,預設情況下,service是執行在主執行緒中,因此你通常仍然需要建立子執行緒執行service任務(若執行阻塞操作或耗時操作)。

基本用法

為了建立一個service,我們必須實現一個Service的子類(或Service的存在的子類)。通常,需要覆蓋一些父類的回撥方法,以此實現service生命週期的關鍵部分,同時如果需要的話,還要提供元件繫結它的機制(後面有詳細介紹繫結的過程)。我們應該覆蓋的主要方法如下:

  • onStartCommand() 
    當一個元件通過startService()啟動一個service時,系統會回撥該方法。一旦該方法執行,service就會在後臺無限制地執行。因此,你必須負責對該service的停止(通過呼叫stopSelf()stopService())。如果只是想繫結service,則不需要實現該方法。 
    每次startService()都會執行該方法。
  • onBind() 
    當另一個元件想要通過bindService()繫結service時,系統會回撥該方法。該方法需要實現一個介面,通過返回IBinder物件實現使用者通訊。若不需要繫結service則返回null即可,否則必須實現。
  • onCreate() 
    當service第一次被建立時,系統會呼叫該方法,該方法在上述兩個方法之前呼叫,且只會執行一次,用來一次性的操作。若service處於執行狀態,則系統不會回撥該方法。
  • onDestroy() 
    當service不再被使用,準備銷燬時,系統呼叫該方法銷燬service。這是service最後被呼叫的方法,通常用於釋放資源。

當元件通過startService()方式啟動service時,系統就會回撥onStartCommand()方法,然後直到該service方法呼叫stopSelf()結束自己或另一個元件呼叫stopService()方法結束該service,它才會停止並被銷燬。

當一個元件通過bindService()方式建立service時(系統不會呼叫onStartCommand()),那麼只要該元件繫結它,它就一直處於執行狀態。一旦service被所有的元件解綁,那麼系統就會銷燬該service。

系統會在記憶體不足時強制殺死service,以保證當前獲得焦點的activity能夠獲得系統資源。若繫結service的activity處於執行狀態,則不太可能銷燬該service,同時若該service被宣告為前臺service(run in the foreground,startForegroud()設定為前臺service),則它最不可能被殺死。否則,started service執行時間越長,被銷燬的概率越大(執行時間越長所在後臺任務中的位置越低,越容易被銷燬)。因此,如果我們需要啟動service的話,最好要設定好如何通過系統重新啟動該service。如果系統銷燬了service,系統通過onStartCommand()方法的返回值判斷是否要重新啟動該service(關於重啟稍後詳解)。

android系統企圖維持擁有service(started的或繫結的)程序儘可能長的時間。當記憶體不足需要kill程序時,下面幾種情況,擁有service的程序生存的概率會提高:

  • 當service在執行onCreate(), onStartCommand(), or onDestroy()程式碼時,宿主程序將會變成前臺程序,以確保這些方法能夠在被kill之前執行完;
  • 若service已經啟動,宿主程序重要性要低於正在使用者螢幕上顯示的程序,但是比其它不可見的程序要高。通常只有少說的程序可見,這就意味著該宿主程序不會被殺死,除非記憶體嚴重不足。然而,由於使用者沒有直接感覺到後臺service的存在,因此,該service(宿主程序)會是一個被kill的候選物件,我們要防止好這種情況發生。執行時間越長被殺死的概率越大,執行的時間足夠長的話,肯定會被kill。
  • 若客戶端繫結一個service,那麼該service的宿主程序的重要性比該客戶端的程序重要(比最重要的客戶端還要重要)。即,若其中繫結service的一個客戶端處於可見狀態,則service被認為是可見的(重要的)。客戶端的重要性影響service的重要性會根據下面的引數調整:BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, BIND_WAIVE_PRIORITY, BIND_IMPORTANT, and BIND_ADJUST_WITH_ACTIVITY
  • 可以通過startForeground(int, Notification)設定一個service為前臺service,這種情況下,系統會認為擁有該service的程序正和使用者進行互動,當記憶體不足時不會被kill。儘管理論上是有可能在記憶體嚴重不足時該前臺service被kill,但是實際上這種情況往往不會發生。
  • 當有其他元件在該程序中,該程序的重要性當然會比只有service一個的重要。

上面情況就說明了,在記憶體嚴重不足時,service可能會被kill,因此要做好準備如何處理這些情況。可以通過onStartCommand()的返回值決定後續是否在執行以及如何執行service。

下面將詳細介紹如何建立service(兩種),以及如何使用。

在manifest檔案中宣告service

同activities(或其他元件)一樣,若要使用service必須在配置檔案中宣告被使用的service。在<application>標籤中通過<service>子標籤可以宣告,如下:

<code class="language-xml hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">manifest</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">...</span> ></span>
  ...
  <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">application</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">...</span> ></span>
      <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">service</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">android:name</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".ExampleService"</span> /></span>
      ...
  <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">application</span>></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">manifest</span>></span>
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

下面詳細介紹<service>標籤中的各個屬性。

<service>

<service>標籤中有如下屬性:

  • Android:enabled="true/false" 
    表明service是否可以由系統例項化,預設”true”為可以。 
    需要注意的是<application>標籤中同樣有該屬性,且作用於整理應用元件。這樣,只有兩個都是”true”(預設是的),service才能例項化。
  • Android:exported="true/false" 
    是否其他應用程式中的元件可以呼叫或和該service進行互動,”true”表示可以,”false”表示不可以。當為”false”時,同一個或相同user ID的應用程式中的元件才能啟動或繫結它。 
    預設值依賴於該標籤是否有intent filters。若沒有的話則表明它必須通過指定類名才能呼叫該service,也就是說只能在應用內部使用(其它程式不知道該類名),此時預設值為”false”。否則,預設值為”true”。 
    當然這個屬性不是唯一限定service是否可以被外部程式使用。我們可以通過permission屬性(下面有介紹)限制外部程式和該service之間的互動。
  • Android:icon 
    代表service的圖示,值為圖片檔名。若不設定這個的話,<application>標籤中的圖示為該service預設圖示。同樣這個圖示又是其子標籤(<intent-filter>)的預設值。
  • Android:isolatedProcess="true/false" 
    若設定為”true”,則service將會在一個特殊的程序中執行,該程序獨立於系統其他程序,且沒有自己的許可權。其他元件和該service唯一的通訊方式是通過Service API(啟動和繫結)。
  • Android:label="String" 
    該service呈現給使用者的名字。若不設定這個的話,<application>標籤中的為該service預設名字。同樣這個名字又是其子標籤(<intent-filter>)的預設值。
  • Android:name 
    我們實現的Service子類的類名,應該是全稱。但是,若包名在根標籤<manifest>中設定的話,則可以簡寫為點加類名。 
    一旦你釋出你的應用後就不應該再改變其值(除非exported屬性設定為false)。原因很簡單,若改變的話,通過Intent指定的類名也需要相應的修改。 
    該值沒有預設值,必須指定。
  • Android:permission="String" 
    許可的名稱(String型別),其他元件若想啟動或繫結該service必須有該許可。若沒有該許可,startService()/bindService()/stopService()就不會起作用,且Intent物件也不會傳遞給service。若沒有設定,則將使用<application>標籤中許可,若都沒有設定則service不受許可的保護。
  • Android:process="String" 
    該service所在程序的名字。通常,所有的元件都執行在應用程式建立的程序中,和應用程式包名相同的名字。在<application>標籤中的process屬性可以設定其它預設程序。但是元件可以設定自己的程式,從而能夠實現多程序傳遞應用程式。 
    若設定為以”:”開頭的名字,則該程序是應用程式一個新的私有的程序,同時它會在需要時被建立,service執行在該程序中。若設定為以小寫字母開頭的名字,該service會執行在一個全域性的程序(名字為該屬性值)中,這樣能夠使其他應用程式的元件共享該程序,減少資源使用。

另外,為了確保安全,通常使用顯示Intent方式去啟動或繫結service,並且不要宣告intent filters。若非得隱士方式的話,你可以使用intent filters並且需要在Intent中排除元件名,但是你必須設定intent的setPackage()方法指定包名,來保證充分的無歧義條件啟動目標service。

另外通過android:exported屬性避免其它應用程式使用你的service,即使它們指定顯示Intent。通常我們的服務不希望外部程式使用,因此該值設定為”false”。

Started Service

前面介紹了通過startService()方式啟動的service為”started”方式,該方法會傳遞一個Intent物件,而在Service中系統會呼叫onStartCommand()方法接收該引數。

當started service啟動之後,它的生命週期獨立於啟動它的元件,並且該service能夠不受限制地執行在後臺,即使啟動它的元件已經被銷燬。因此,該service必須在完成任務時呼叫stopSelf()或另一個元件通過stopService()停止它。

注意:預設情況下,service會執行在主執行緒中,因此當執行頻繁的或阻塞的操作時,需要開啟一個新的子執行緒來完成service任務。

通常,有兩個類可以用於繼承並建立started service:

  • Service類 
    所有services的抽象基類。通過這種方式,開啟新執行緒執行service任務是非常重要的,以防阻塞UI執行緒。
  • IntentService類 
    這是Service類的一個子類,該類使用一個子執行緒處理所有的請求,且同一時間只處理一個請求。如果你不需要你的service同時處理多個請求的話這種方式實現的service最好。你只需要實現onHandleIntent()方法來接收傳遞過來的intent,那麼就可以在後臺執行處理了。 
    IntentService是一個抽象類,但是除了onHandleIntent()方法之外的方法都實現了。

下面介紹如何實現上面的描述。

繼承IntentService類

IntentService類是一個抽象類,是service處理非同步請求的一個基類。在客戶端通過startService()方式傳送請求時,IntentService實現類例項在一個工作執行緒中處理所有的請求(每次只處理一個請求),並且在執行完所有操作時,自動停止銷燬。

工作佇列處理模式常用於不在主執行緒中執行的任務。IntentService類簡化了這種模式,並充分利用了這種機制。為了使用這種方法,需要繼承IntentService類並實現onHandleIntent(Intent)方法(而不是覆蓋onStartCommand()方法)。IntentService將開啟一個工作執行緒處理接收到的Intents,並在合適的時候停止service。所有的請求都在一個工作執行緒中,它們可能花費很長時間(但是不會阻塞主執行緒),但是每次只一個請求被執行。

通常來說,我們不需要service同時處理多個請求,因此通過這種方式可能是最好的。

IntentService會完成以下內容:

  • 建立一個預設的子執行緒(不同於程式的主執行緒)處理通過onStartCommand()傳遞過來的所有Intent物件。
  • 建立一個工作佇列(Handler實現的),該佇列一次只處理一個Intent物件(通過onHandleIntent()處理),因此不用擔心多執行緒造成的問題。
  • 在處理完所有的請求時,該service會自動停止,因此,我們不用再通過stop方式停止。
  • 提供預設的實現onBind()方法,返回null。
  • 提供一個預設的實現onStartCommand(),該方法能夠將intent物件傳送到work佇列中,然後通過onHandleIntent()處理。

根據上面可知,我們僅僅需要實現onHandleIntent()來處理客戶端傳遞過來的Intent物件。當然還需要一個構造器呼叫父類的構造器,為子執行緒命名(只在除錯中有用),該構造器是必須的。

如果我們需要實現其他的回撥方法的話,例如onCreate(),onStartCommand(),onDestroy(),需要呼叫父類對應的方法。例如onStartCommand()必須返回父類方法,這樣系統才能管理好IntentService的生命週期。注意通常不需要呼叫父類的onStartCommand()方法。

IntentService中其它的方法有onBind()(預設返回null),setIntentRedilivery(boolean)(若為true,則onStartCommand()返回START_REDELIVER_INTENT,因此在onHandleIntent()返回之前程序被殺死的話,程序將重啟並傳遞最近的一個intent。若為false(預設),則onStartCommand()返回START_NOT_STICKY)。

程式碼就不貼了,只說明一下測試結果。

  1. 通過檢視原始碼可知,IntentService中呼叫了stopSelf(int),而且測試表明在完成任務時可以自動銷燬(呼叫onDestroy()方法);
  2. 每次只執行一個Intent物件請求;

下面將介紹如何使用繼承Service類實現service,該service適合同時處理多個start請求。

繼承Service類

通過繼承Service類實現service可以處理多執行緒問題。和繼承IntentService類不同的是,我們需要自己實現onStartCommand()方法,這樣一來我們可以同時處理多執行緒問題,而不是在工作佇列中等待。

onStartCommand()方法必須有一個int返回值,該返回值描述了當系統kill該service之後系統應該如何繼續使用該service(IntentService中有預設值)。該返回值必須是取一下內容之一:

  1. START_NOT_STICKY(值為2) 
    設定該值,如果在返回過onStartCommand()方法之後系統殺死了該service,則該Service不會再被系統重建,除非有後續的intents。因此在不需要重新執行service或應用程式能夠重新啟動該service的情況下,這個返回值是最安全的選擇。

    這種設定也是有意義的,當一開始啟動service時完成一些任務,當記憶體不足時被殺死,之後我們可以在記憶體夠用的情況下再啟動它,繼續完成任務。

  2. START_STICKY(值為1) 
    設定該值,如果在返回過onStartCommand()方法之後系統殺死了該service(此時記錄該service為started 狀態),則之後系統會嘗試重新建立該service並建立之後一定呼叫onStartCommand()方法,但是不會傳遞最後一個Intent物件,而是用值為null的Intent物件(因此需要小心判斷),除非有後續的intents。 
    這種設定適合像媒體播放器之類的service,不需要執行命令,只要獨立執行並等待任務。

  3. START_REDELIVER_INTENT(值為3) 
    設定該值,如果在返回過onStartCommand()方法之後系統殺死了該service,則之後系統會重新建立該service並呼叫onStartCommand()方法,且該Intent值為最後一次Intent物件。這種適合需要立即執行的任務。

每次請求都會執行onStartCommand()方法,然而只要一次stopService()或stopSelf()就能停止。

停止一個service

顯然,這裡是指繼承Service類。 
started方式啟動的service必須管理好自己的生命週期。也就是,除非系統在回收記憶體,否則service不會自己停止或銷燬。因此必須通過stopSelf()或在其它元件中呼叫StopService(),一旦執行了這兩個方法中的一個,系統就會盡可能地銷燬該service。

在處理多個請求時,若執行完一個請求時呼叫stopSelf()方法或StopService()的話,後續的請求就就不會再執行,因此,通常呼叫stopSelf(int)方法,根據ID(onStartCommand()方法中傳遞的startId),停止指定的一次請求。

注意:當service完成任務時,停止該service(自身或外界)是必要的,這樣能夠避免浪費資源以及耗電,包括在started之後又繫結的service。

程式碼不再給出,結果測試:

  1. service預設執行在主執行緒中,因此若service需要長時間的操作會造成ANR問題(BACK也沒反應);
  2. startService()方法是非同步執行,立刻執行它下面的語句;
  3. 無論多少客戶端(例如activity),不管在不在一個程序中,啟動service時,只要沒有執行stopService()(任意客戶端都能執行)或stopself()onCreate()只執行一次,即service例項只有一個。除非service被kill(不執行onDestroy()),否則onDestroy()也只執行一次。
  4. stopService()/startService()不會有任何異常(只要輸入的是Intent物件),但是在錯誤輸入的情況下沒有任何反應。
  5. 關於提到的service可以同時處理多個請求,而IntentService只能處理一個的問題,我的理解是service開啟執行緒的話可以併發執行,而IntentService自身是一個工作佇列,不用再開啟執行緒(當然也可以在onHandleIntent()中開啟執行緒,但是對於IntentService還是同一時刻處理一個Intent物件)。
  6. service和IntentService中若開啟執行緒的話,不再受到service的限制,即使service銷燬,執行緒依然可以執行。

管理Service生命週期

由於service是執行在後臺的,因此管理service的生命週期需要加倍小心。

有兩種方式的service,因此從建立到銷燬有兩種生命週期:

  • started service 
    對於這種方式的service,當另一個組建呼叫startService()時,該service被建立,然後該service不受限制的執行,同時我們必須在該service完成任務時停止它。當service被停止時,系統就會銷燬它。
  • bound service 
    對於這種方式的service,當另一個組建呼叫bindService()時,該service被建立,客戶端通過IBinder介面和該service通訊。客戶端可以通過unBindService()解除繫結。多個客戶端可以繫結同一個service,當所有的客戶端都解綁時,系統自動銷燬該service,不用客戶端銷燬。

當然這兩種方式不是絕對分開的。我們可以先啟動一個service然後再進行繫結。這種情況下呼叫stopSelf()方法或StopService()方法只有在所有組建都解綁該service時才會停止該service。(這一部分在下一章有更詳細的介紹)

實現週期方法

同activity一樣,我們可以實現生命週期回撥方法來監控service狀態的改變,以到達在合適的時間做合適的事。

如下圖。左邊部分描述的是通過startService()方法啟動的service的生命週期,右邊部分是通過bindService()方法繫結的service的生命週期。 
這裡寫圖片描述

  • The entire lifetime 
    從建立到銷燬的過程。一般在onCreate()完成初始化,在onDestroy()中釋放所有資源。例如,音樂service在onCreate()建立一個執行緒來播放音樂,在onDestroy()中停止執行緒。無論在startService()還是bindService()都會呼叫onCreate()onDestroy()
  • The active lifetime 
    開始於onStartCommand()onBind()。各自通過各自的方式傳遞Intent物件。 
    started service的active lifetime的結束時間是entire lifetime結束時間。bound service active lifetime的結束時間在返回onUnbind()時。

注意:

  • 在service生命週期中沒有onStop()回撥方法。
  • 雖然上圖的生命週期是分開的,但是一個started service在呼叫onStartCommand()(通過startService())之後,仍然可以呼叫onBind()(通過bindService())。

轉自http://blog.csdn.net/wangyongge85/article/details/46873203