天天看點

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

本節書摘來自異步社群《android開發進階:從小工到專家》一書中的第1章,第1.2節service與aidl,作者 何紅輝,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

1.2 service與aidl

service是android中實作程式背景運作的解決方案,它非常适合用于去執行那些不需要和使用者互動而且還要求長期運作的任務。但不要被“背景”二字所迷惑,service預設并不會運作在子線程中,它也不運作在一個獨立的程序中,它同樣執行在ui線程中,是以,不要在service中執行耗時的操作,除非你在service中建立了子線程來完成耗時操作。

service的運作不依賴于任何使用者界面,即使程式被切換到背景或者使用者打開了另外一個應用程式,service仍然能夠保持正常運作,這也正是service的使用場景。當某個應用程式程序被殺掉時,所有依賴于該程序的service也會停止運作。

1.2.1 普通service

service的生命周期相對activity來說簡單得多,隻有3個,分别為oncreate、onstartcommand和ondestory。一旦在項目的任何位置調用了context 的startservice()函數,相應的服務就會啟動起來,首次建立時會調用oncreate函數,然後回調onstartcommand()函數。服務啟動了之後會一直保持運作狀态,直到stopservice()或stopself()函數被調用。雖然每調用一次startservice()函數,onstartcommand()就會執行一次,但實際上每個服務都隻會存在一個執行個體。是以不管你調用了多少次startservice()函數, 隻需調用一個stopservice()或stopself()函數,服務就會被停止。

通常的service大緻如下:

與activity一樣,service也需要在androidmanifest.xml中進行注冊,示例如下:

上述示例表示注冊一個在應用包service目錄下的myservice服務,注冊之後,當使用者調用startservice(new intent(mcontext,myservice.class)) 時會調用onstartcommand函數,我們在該函數中調用domyjob,而在domyjob中我們建立了一個線程來執行耗時操作,以避免阻塞ui線程。當我們的service完成使命時,需要調用stopservice來停止該服務。

1.2.2 intentservice

完成一個簡單的背景任務需要這麼麻煩,android顯然早就“洞察”了這一點。是以,提供了一個intentservice來完成這樣的操作,intentservice将使用者的請求執行在一個子線程中,使用者隻需要覆寫onhandleintent函數,并且在該函數中完成自己的耗時操作即可。需要注意的是,在任務執行完畢之後intentservice會調用stopself自我銷毀,是以,它适用于完成一些短期的耗時任務。示例如下:

1.2.3 運作在前台的service

service預設是運作在背景的,是以,它的優先級相對比較低,當系統出現記憶體不足的情況時,它就有可能會被回收掉。如果希望service可以一直保持運作狀态,而不會由于系統記憶體不足被回收,可以将service運作在前台。前台服務不僅不會被系統無情地回收,它還會在通知欄顯示一條消息,下拉狀态欄後可以看到更加詳細的資訊。例如,墨迹天氣在前台運作了一個service,并且在service中定時更新通知欄上的天氣資訊,如圖1-11所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

下面我們就來實作一個類似于如圖1-11所示的效果,首先我們定義一個服務,代碼如下:

我們在oncreate函數中調用了shownotification函數顯示通知,并且在最後調用startforeground将服務設定為前台服務。在androidmanifest.xml注冊之後我們就可以啟動該service了。效果如圖1-12所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

1.2.4 aidl(android接口描述語言)

aidl(android接口描述語言)是一種接口描述語言,通常用于程序間通信。編譯器根據aidl檔案生成一個系列對應的java類,通過預先定義的接口以及binder機制達到程序間通信的目的。說白了,aidl就是定義一個接口,用戶端(調用端)通過bindservice來與遠端服務端建立一個連接配接,在該連接配接建立時會傳回一個ibinder對象,該對象是服務端binder的binderproxy,在建立連接配接時,用戶端通過asinterface函數将該binderproxy對象包裝成本地的proxy,并将遠端服務端的binderproxy對象指派給proxy類的mremote字段,就是通過mremote執行遠端函數調用。

在用戶端建立一個aidl檔案,如圖1-13所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

在ssoauth.aidl檔案中會預設有一個basictypes函數,我們在程式後面添加一個ssoauth的函數用于sso授權。代碼如下:

因為用戶端是調用端,是以,隻需要定義aidl檔案,此時rebuild一下工程就會生成一個ssoauth.java類,該類根據ssoauth.aidl檔案生成,包含了我們在aidl檔案中定義的函數。因為aidl通常用于程序間通信,是以,我們建立一個被調用端的工程,我們命名為aidl_server,然後将用戶端的aidl檔案夾複制到aidl_server的app/src/main目錄下,結構如圖1-14所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

此時相當于在用戶端和被調用端都有同一份ssoauth.aidl檔案,它們的包名、類名完全一緻,生成的ssoauth.java類也完全一緻,這樣在遠端調用時它們就能夠擁有一緻的類型。rebuild被調用端工程之後就會生成ssoauth.java檔案,該檔案中有一個stub類實作了ssoauth接口。我們首先需要定義一個service子類,然後再定義一個繼承自stub的子類,并且在service的onbind函數中傳回這個stub子類的對象。示例代碼如下:

從上述代碼中我們看到,實際上完成功能的是繼承自stub的sinassoimpl類,service隻提供了一個讓sinassoimpl依附的外殼。完成sinassoauthservice之後我們需要将它注冊在被調用端應用的manifest中,注冊代碼如下:

然後先運作被調用端(也就是server端)應用,并且在用戶端中完成調用server的代碼。用戶端activity的代碼如下:

在上述activity程式中,運作程式後點選登入按鈕時會向server端發起連接配接service請求,在建立連接配接之後會将binder對象轉換為ssoauth對象,然後調用ssoauth對象的ssoauth函數。此時的ssoauth函數實際上調用的就是server端中sinassoimpl類的實作。運作程式後點選登入按鈕,如圖1-15所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

這一切的核心都是通過aidl檔案生成的stub類以及其背後的binder機制。首先我們看看生成的ssoauth.java,stub類就是該檔案中的内部類。代碼如下:

在ssoauth.java中自動生成了ssoauth接口,該接口中有一個ssoauth函數。但最,重要的是生成了一個stub類,該類繼承自binder類,并且實作了ssoauth接口。stub裡面最重要的就是asinterface()這個函數,在這個函數中會判斷obj參數的類型,如果該obj是本地的接口類型,則認為不是程序間調用,此時将該obj轉換成ssoauth類型;否則會通過自動生成的另一個内部類proxy來包裝obj,将其指派給proxy中的mremote字段。proxy類也實作了ssoauth接口,不同的是它是通過binder機制來與遠端程序進行互動,例如,在ssoauth ()函數中,proxy将通過binder機制向服務端傳遞請求和資料,它請求的類型為transaction_ssoauth,參數分别是string類型的username和pwd。

對于服務端代碼來說,它也有同一份ssoauth.aidli以及ssoauth.java,但不同的是服務端是指令的接收端,用戶端的調用會通過binder機制傳遞到服務端,最終調用stub類中的ontransact函數。可以看到在case transaction_ssoauth處執行了this.ssoauth()函數,意思是當接收到用戶端的transaction_ssoauth請求時,執行this.ssoauth()函數,通過用戶端的分析我們知道,當我們調用ssoauth()時實際上就是通過mremote向服務端送出了一個transaction_ssoauth請求,是以,這兩端通過binder機制就對接上了,我們可以簡單地了解為c/s模式。

而在用戶端調用bindservice之後,如果綁定成功則會調用onserviceconnected(componentname name,ibinder service),這裡的service對象是binderproxy類型,經過asinterface轉換後被包裝成了proxy類型,但是調用的時候,執行的是服務端sinassoimpl中的ssoauth()函數。是以, sinassoimpl執行個體mbinder被服務端包裝成binderproxy類型,再經過用戶端的proxy進行包裝,通過binder機制進行資料傳輸,實作程序間調用。

它們的調用時序圖如圖1-17所示。

《Android開發進階:從小工到專家》——第1章,第1.2節Service與AIDL

打個比方說,有兩個公司打算進行合作需要進行業務磋商,并且這次合作已經簽署了合同,隻剩下一些細節沒有最終确定。但是由于大boss比較忙,是以各自都派了一個代表進行溝通。由于兩家公司相距較遠,雙方代表都通過電話進行溝通。boss-a跟代表-a交代說,這次合作對方支付的酬勞不能低于十塊錢,于是代表-a通過電話與代表b進行溝通,代表-b得到消息之後跑到boss-b的辦公室請示,boss-b确認之後又由代表-b回複代表-a,代表-a最終回報給boss-a。這個例子中的兩個boss分别對應用戶端和服務端,合同就對應了ssoauth接口,而兩個代表則對應了兩端的proxy,代表的通信方式則是電話,而代碼的通信方式是binder。

總體來說,使用aidl并不是一件困難的事,但是了解aidl的機制确實有一定的難度。也正是如此,android通過aidl這個機制将一些複雜的概念與邏輯通過自動生成類型的方式屏蔽掉,使得開發人員能夠更簡單地進行程序間通信。

繼續閱讀