【前言】
花了周末兩天的時間,整理了一下作為android四大元件之一的service的基礎知識,通過這篇文章,應該可以明白:對service的了解、在什麼地方使用、怎麼使用、要注意哪些問題等。
【本文主要内容】
一、service的基本概念(四大元件之一)
二、定義(啟動)一個service
1、如何定義(啟動)一個service:
2、停止一個started服務有兩種方法
3、onstartcommand方法的傳回值
三、intentservice
1、intentservice的引入
2、intentservice的作用
3、intentservice的用法
4、service和thread的關系
四、使用bind service完成service和activity之間的通信
1、bind service的介紹
2、實作service和activity之間通信步驟
3、started服務與bind服務的差別
4、service的生命周期
五、使用bind service完成ipc程序間通信:(在同一個app内模拟)
2、在用戶端綁定一個服務的步驟
3、ipc(inter-process communication)程序間通信機制
4、aidl(android interface definition language)android接口定義語言
5、ipc(程序間通訊)具體的步驟如下
6、讓activity與一個遠端service建立關聯的步驟:(在同一個app内模拟)
7、aidl支援的自定義資料類型
六、使用bind service完成ipc程序間通信:(兩個app之間)
七、messenger的使用
【正文】
service是android中實作程式背景運作的解決方案,非常适合用于去執行哪些不需要和使用者互動而且還要求長期運作的任務。不能運作在一個獨立的程序當中,而是依賴與建立服務時所在的應用程式程序。隻能在背景運作,并且可以和其他元件進行互動。
service可以在很多場合使用,比如播放多媒體的時候使用者啟動了其他activity,此時要在背景繼續播放;比如檢測sd卡上檔案的變化;比如在背景記錄你的地理資訊位置的改變等等,總之服務是藏在背景的。
二、定義(啟動)一個service:
核心步驟如下:
建立一個類繼承android.app.service類,實作抽象方法onbind(),重寫oncreate()、onstartcommand()、ondestry();
在清單檔案中配置service。
建立一個android項目servicetest,具體步驟如下:
(1)建立一個myservice類,繼承自service,并重寫父類的oncreate()、onstartcommand()和ondestroy()方法,代碼如下:


可以看到,我們隻是在oncreate()、onstartcommand()和ondestroy()方法中分别列印了一句話,并沒有進行其它任何的操作,注意代碼注釋中這三個方法的作用。
onbind()方法是service中唯一的一個抽象方法,是以必須要在子類裡實作。我們知道,service可以有兩種啟動方式:一種是startservice(),另一種是bindservice()。第二種啟動方式才會用到onbind()方法。我們這先用第一種方式啟動service,是以暫時忽略onbind()方法。
(2)在清單檔案中聲明:(和activity标簽并列)
(3)修改activity_main.xml代碼,如下:


我們在布局檔案中加入了兩個按鈕,一個用于啟動service,一個用于停止service。
(4)在mainactivity作為程式的主activity,在裡面加入啟動service和停止service的邏輯,代碼如下:


核心代碼:31行至32行、35行至36行。
可以看到,在start service按鈕的點選事件裡,我們建構出了一個intent對象,并調用startservice()方法來啟動myservice。然後在stop serivce按鈕的點選事件裡,我們同樣建構出了一個intent對象,并調用stopservice()方法來停止myservice。代碼的邏輯非常簡單。
這樣的話,一個簡單的帶有service功能的程式就寫好了。
啟動和停止服務:
定義好服務之後,接下來看一下如何啟動和停止一個服務,這主要是借助intent來實作的。注意startservice()和stopservice()方法都是定義在context類當中的,是以可以在mainactivity中直接調用這兩個方法。
運作上面的程式,點選button1_start_service按鈕,啟動服務,背景列印日志如下:
說明服務啟動成功。
那麼如果我再連續點三次button1_start_service按鈕,背景增加的日志如下:
事實上,oncreate()方法隻會在service第一次被建立的時候調用,而onstartcommand()方法在每次啟動服務的時候都會調用。
我們還可以在正在“設定--應用---運作”中找到這個服務,如下圖所示:
點開上圖中的紅框部分,可以看到:
如果我們再點選button2_stop_service按鈕或者點選上圖中的“stop”,myservice服務就停止掉了:
需要注意的是:
服務對象同時隻會有一個
預設情況下,一個started的service與啟動他的元件在同一個線程中。上面的執行個體中,服務就是在主線程中運作的,如果是在服務中完成耗時操作的話,容易造成主線程阻塞。
2、停止一個started服務有兩種方法:
(1)在外部使用stopservice()
(2)在服務内部(onstartcommand方法内部)使用stopself()方法。
3、onstartcommand方法的傳回值:
onstartcommand方法執行時,傳回的是一個int型。這個整型可以有三個傳回值:start_not_sticky、start_sticky、start_redeliver_intent
start_not_sticky:“非粘性的”。使用這個傳回值時,如果在執行完onstartcommand方法後,服務被異常kill掉,系統不會自動重新開機該服務。
start_sticky:如果service程序被kill掉,保留service的狀态為開始狀态,但不保留遞送的intent對象。随後系統會嘗試重新建立service,由于服務狀态為開始狀态,是以建立服務後一定會調用onstartcommand(intent,int,int)方法。如果在此期間沒有任何啟動指令被傳遞到service,那麼參數intent将為null。
start_redeliver_intent:重傳intent。使用這個傳回值時,系統會自動重新開機該服務,并将intent的值傳入。
1、intentservice的引入:
我們在第一段中就已經說了,服務中的代碼預設運作在主線程中,如果直接在服務裡執行一些耗時操作,容易造成anr(application not responding)異常,是以就需要用到多線程的知識了。
是以一個比較标準的服務可以這樣寫:


核心代碼:14至19行,在子線程中處理具體的邏輯。
需要注意的是,如果沒有第17行的stopself(),服務一旦啟動後,就會一直處于運作狀态,必須調用stopservice()或者stopself()方法才能讓服務停止下來;是以我們添加了17行的stopself(),服務執行完畢後會自動停止。
雖說上面的這種寫法并不複雜,但總會有一些程式猿忘記開啟線程,或者忘記調用stopself()方法。為了可以簡單地建立一個異步的、會自動停止的服務,android專門提供了一個intentservice類,這個類就很好的解決了上面所提到的兩種尴尬。另外,可以啟動intentservice多次,而每一個耗時操作會以工作隊列的方式在intentservice的onhandleintent()回調方法中執行,并且每次隻會執行一個工作線程,執行完第一個後,再執行第二個,以此類推。
2、intentservice的作用:
當我們需要這樣一次性完成的任務時,就可以使用intentservice來完成。
3、intentservice的用法:
我們在上面的項目servicetest基礎上進行修改,步驟如下:
(1)建立一個myintentservice類,繼承自intentservice,并重寫父類的onhandleintent()方法,代碼如下:


這裡首先要提供一個無參的構造方法,并且必須在其内部調用父類的有參構造方法(9至12行),我們在第10行手動将服務的名字改為“myintentservice”。
然後在子類中實作onhandleintent()這個抽象方法,可以在這個方法裡去處理一些具體的邏輯,我們就用三次for循環,列印目前線程的id,每次延時1秒。
因為這個服務在運作結束後會自動停止,是以我們在ondestroy()方法中列印日志驗證一下。
(2)在清單檔案中對服務進行注冊服務:
(3)在activity_main.xml中添加一個按鈕button3_stop_intentservice,用于啟動myintentservice服務,代碼略。
(4)在mainactivity裡面加入啟動intentservice的邏輯,核心代碼如下:
我們在第02行中,列印主線程的id。
運作程式,點選按鈕button3_stop_intentservice,顯示如下:
由此可見,啟動一個intentservice和啟動一個普通的service,步驟是一樣的。
4、service和thread的關系:
不少android初學者都可能會有這樣的疑惑,service和thread到底有什麼關系呢?什麼時候應該用service,什麼時候又應該用thread?答案可能會有點讓你吃驚,因為service和thread之間沒有任何關系!
之是以有不少人會把它們聯系起來,主要就是因為service的背景概念。thread我們大家都知道,是用于開啟一個子線程,在這裡去執行一些耗時操作就不會阻塞主線程的運作。而service我們最初了解的時候,總會覺得它是用來處理一些背景任務的,一些比較耗時的操作也可以放在這裡運作,這就會讓人産生混淆了。但是,如果我告訴你service其實是運作在主線程裡的,你還會覺得它和thread有什麼關系嗎?
其實,背景和子線程是兩個完全不同的概念:
android的背景就是指,它的運作是完全不依賴ui的。即使activity被銷毀,或者程式被關閉,隻要程序還在,service就可以繼續運作。比如說一些應用程式,始終需要與伺服器之間始終保持着心跳連接配接,就可以使用service來實作。你可能又會問,service既然是運作在主線程裡,在這裡一直執行着心跳連接配接,難道就不會阻塞主線程的運作嗎?當然會,但是我們可以在service中再建立一個子線程,然後在這裡去處理耗時邏輯就沒問題了。
既然在service裡也要建立一個子線程,那為什麼不直接在activity裡建立呢?這是因為activity很難對thread進行控制,當activity被銷毀之後,就沒有任何其它的辦法可以再重新擷取到之前建立的子線程的執行個體;而且在一個activity中建立的子線程,另一個activity無法對其進行操作。但是service就不同了,所有的activity都可以與service進行關聯,然後可以很友善地操作其中的方法,即使activity被銷毀了,之後隻要重新與service建立關聯,就又能夠擷取到原有的service中binder的執行個體。是以,使用service來處理背景任務,activity就可以放心地finish,完全不需要擔心無法對背景任務進行控制的情況。
是以說,一個比較标準的service,就可以寫成本段中第1節的樣子。
有沒有什麼辦法能讓它們倆的關聯更多一些呢?比如說在activity中指揮service去幹什麼,service就去幹什麼。當然可以,隻需要讓activity和service建立關聯就好了。
1、bind service的介紹:
應用程式元件(用戶端)通過調用bindservice()方法能夠綁定服務,然後android系統會調用服務的onbind()回調方法,則個方法會傳回一個跟伺服器端互動的binder對象。
這個綁定是異步的,bindservice()方法立即傳回,并且不給用戶端傳回ibinder對象。要接收ibinder對象,用戶端必須建立一個serviceconnection類的執行個體,并且把這個執行個體傳遞給bindservice()方法。serviceconnection對象包含了一個系統調用的傳遞ibinder對象的回調方法。
注意:隻有activity、service、content provider能夠綁定服務;broadcastreceiver廣播接收器不能綁定服務。
2、實作service和activity之間通信步驟:
我們依然在第二段中的項目servicetest基礎上進行修改。
觀察上面第二段中myservice中的代碼,你會發現一直有一個onbind()方法我們都沒有使用到,這個方法其實就是用于和activity建立關聯的,修改myservice中的代碼,如下所示:


38至50行:建立一個mybinder類,繼承binder:讓裡面的方法執行下載下傳任務,并擷取下載下傳進度。當然,這裡隻是兩個模拟方法,并沒有實作真正的功能,我們通過列印日志的形式來展現。
接着建立mybinder的執行個體(13行),然後在onbind()方法裡傳回這個執行個體(35行)。
核心代碼是35行,傳回這個mbinder,是一個ibinder類型,就可以把這個ibinder類型傳遞到mainactivity中,進而調用service裡面的方法。下面就要看一看,在mainactivity是如何調用service裡面的兩個方法的。
(2)檢查清單檔案,是否已經對service進行注冊:
(3)在activity_main.xml中繼續添加兩個按鈕button3_bind_service和button4_unbind_service,用于綁定服務和取消綁定服務。最終,activity_main.xml的完整代碼如下:


(4)接下來再修改mainactivity中的代碼,讓mainactivity和myservice之間建立關聯,代碼如下所示:


可以看到,這裡我們首先建立了一個serviceconnection的匿名類(24行),在裡面重寫了onserviceconnected()方法和onservicedisconnected()方法,如果目前activity與服務連接配接成功後,服務會回調onserviceconnected()方法,
在onserviceconnected()方法中,我們又通過向下轉型得到了mybinder的執行個體(34行),有了這個執行個體,activity和service之間的關系就變得非常緊密了。現在我們可以在activity中根據具體的場景來調用mybinder中的任何public方法(36、37行),即實作了activity指揮service幹什麼service就去幹什麼的功能。
當然,現在activity和service其實還沒關聯起來了呢,這個功能是在bind service按鈕的點選事件裡完成的。可以看到,這裡我們仍然是建構出了一個intent對象,然後調用bindservice()方法将activity和service進行綁定。bindservice()方法接收三個參數,第一個參數就是剛剛建構出的intent對象,第二個參數是前面建立出的serviceconnection的執行個體,第三個參數是一個标志位,這裡傳入bind_auto_create表示在activity和service建立關聯後會自動建立service(即使之前沒有建立service也沒有關系),這會使得myservice中的oncreate()方法得到執行,但onstartcommand()方法不會執行。
然後如何我們想解除activity和service之間的關聯怎麼辦呢?調用一下unbindservice()方法就可以了,這也是unbind service按鈕的點選事件裡實作的邏輯。
現在讓我們重新運作一下程式吧,在mainactivity中點選一下bind service按鈕,logcat裡的列印日志如下圖所示:
可以看到,隻點選了bind service按鈕,但是oncreate()方法得到了執行,而onstartcommand()方法不會執行。
另外需要注意,任何一個service在整個應用程式範圍内都是通用的,即myservice不僅可以和mainactivity建立關聯,還可以和任何一個activity建立關聯,而且在建立關聯時它們都可以擷取到相同的mybinder執行個體。
如何銷毀service:
根據上面第一段的知識,我們介紹了銷毀service最簡單的一種情況:現在解除安裝程式,重新運作程式,點選start service按鈕啟動service,再點選stop service按鈕停止service,這樣myservice就被銷毀了:
現在回到本段内容。解除安裝程式,重新開始。那麼如果我們隻點選的bind service按鈕呢?由于在綁定service的時候指定的标志位是bind_auto_create,說明點選bind service按鈕的時候service也會被建立,這時應該怎麼銷毀service呢?其實也很簡單,點選一下unbind service按鈕,将activity和service的關聯解除就可以了:
以上這兩種銷毀的方式都很好了解。那麼如果我們既點選了start service按鈕,又點選了bind service按鈕會怎麼樣呢?這個時候你會發現,不管你是單獨點選stop service按鈕還是unbind service按鈕,service都不會被銷毀,必要将unbind service按鈕和stop service按鈕都點選一下(沒有先後順序),service才會被銷毀。也就是說,點選stop service按鈕隻會讓service停止,點選unbind service按鈕隻會讓service和activity解除關聯,一個service必須要在既沒有和任何activity關聯又處理停止狀态的時候才會被銷毀。
點選unbind service按鈕後,再次點選unbind service按鈕按鈕引發的問題:
假設現在service和activity已經相關聯了,點選unbind service按鈕能夠解除綁定,如果繼續點選unbind service按鈕,程式會異常退出,這說明代碼不夠完善,我們需要在代碼中加一個判斷是否綁定的标記mbound。在改mainactivity中增加一部分代碼,最終改mainactivity的完整代碼如下:(加粗字型是添加的内容)


添加的代碼是第21行、29行、72行至74行。
這樣的話,連續點選unbind service按鈕,就不會使程式出現異常。
3、started服務與bind服務的差別:
差別一:生命周期
通過started方式的服務會一直運作在背景,需要由元件本身或外部元件來停止服務才會以結束運作
bind方式的服務,生命周期就要依賴綁定的元件
差別二:參數傳遞
started服務可以給啟動的服務對象傳遞參數,但無法擷取服務中方法的傳回值
bind服務可以給啟動的服務對象傳遞參數,也可以通過綁定的業務對象擷取傳回結果
實際開發中的技巧;
第一次先使用started方式來啟動一個服務
之後可以使用bind的方式綁定服務,進而可以直接調用業務方法擷取傳回值
4、service的生命周期:
一旦在項目的任何位置調用了context的startservice()方法,相應的服務就會啟動起來,并回調onstartcommand()方法。如果這個服務之前還沒有建立過,oncreate()方法會先于onstartcommand()方法執行。服務啟動過後,會一直保持運作狀态,直到stopservice()或stopself()方法被調用。注意雖然每次調用一次startservice()方法,onstartcommand()方法就會以執行一次,但實際上每個服務都隻會存在一個執行個體。是以不管你調用了多少次startservice()方法,隻需調用一次stopservice()或stopself()方法,服務就會停止。
另外,還可以調用context的bindservice()來擷取一個服務的持久連接配接,這時就會回調服務中的onbind()方法。類似地,如果這個服務之前還沒有建立過,oncreate()方法會先于onbind()方法執行。之後調用方可以擷取到onbind()方法裡傳回的ibinder對象的執行個體,這樣,就能自由地和服務進行通信了。隻要調用方和服務之間的連接配接沒有斷開,服務就會一直保持運作狀态。
既然是在在同一個app内模拟程序間通信,其實就是完成程序内通信,但是原理都是一樣的嘛。
也就是說,要實作:讓activity與一個遠端service建立關聯,這就要使用aidl來進行跨程序通信了(ipc)。這裡把bind service及其他的概念再重複一下:
2、在用戶端綁定一個服務的步驟:
(1)實作serviceconnection抽象類。實作過程中,必須重寫一下兩個回調方法:
onserviceconnected() 和服務綁定成功後,系統會調用這個方法來發送由服務的onbind()方法傳回的ibinder對象
onservicedisconnected() 當服務異常終止時會調用(如服務崩潰或被殺死時)。注意,在用戶端解除綁定時不會調用該方法。
(2)調用bindservice()方法來傳遞serviceconnection類的實作;
(3)當系統調用你的onserviceconnected()回調方法時,你就可以開始使用接口中定義的方法來調用服務了
(4)調用unbindservice()方法斷開與服務的連結。
注:bindservice()和unbindservice()方法都是context類中的方法。
3、ipc(inter-process communication)程序間通信機制:
在同一程序中,各個元件進行通信是十分友善的,普通的函數調用就可以解決;但是對于不同的程序中的元件來說,要進行通信,就需要用到android的ipc機制了。
對應用開發者來說,android的ibinder/binder架構實作了android的ipc通信。當然,ibinder/binder架構也可以用來實作程序内通信(本地通信),也可以實作程序間通信(遠端通信)
從android sdk中對ibinder/binder的解釋可知,ibinder/binder是android遠端對象的基本接口,它是android用于提供高性能ipc通信而設計的一套輕量級遠端調用機制的核心部分。該接口描述了與一個遠端對象進行通信的抽象協定。
4、aidl(android interface definition language)android接口定義語言:
aidl它可以用于讓某個service與多個應用程式元件之間進行跨程序通信,進而可以實作多個應用程式共享同一個service的功能。
aidl支援的類型:八大基本資料類型、string類型、charsequence、list、map、自定義。
來看下面的這張原理圖:
上圖中,如果a應用程式想通路b應用程式中的業務對象,可以先讓a綁定b應用中的service,然後通過service去通路b中的業務對象。我們可以用aidl來描述需要被别人調用的接口(即b中的業務對象)。
5、ipc(程序間通訊)具體的步驟如下:
使用aidl定義業務接口,通過adt工具來生成一個java類,此類實作了程序間遠端通訊的代理
編寫自己的業務類(繼承生成的類中的stub)來實作業務接口功能
再通過綁定service的方式來暴露此業務對象,給其它元件提供功能
調用者元件通過bindservice方法綁定服務,進而擷取綁定成功後的遠端業務對象或本地業務對象,然後就可以調用相關功能。
注意:一般在使用完綁定服務後,需要解除綁定。
下面就通過代碼來實作。
建立一個全新的android工程servicetest02。
(1)建立iperson.aidl檔案,代碼如下所示:


這個檔案裡,添加我們需要的業務方法。第01行是包名。注意不要寫public等修飾符。(如果這個檔案寫錯了,程式會報錯,後面的java檔案也不會自從生成)
檔案儲存之後,adt會在gen目錄下自動生成一個對應的java檔案,如下圖所示:
之後,程式運作的時候使用的是這個java檔案,與aidl檔案就沒有關系了。
我們來大緻分析一下這個自動生成的java檔案。完整版代碼如下:


分析:
這個java檔案實際上是一個接口,同時生成了在aidl檔案中定義的四個方法,并抛出了遠端調用的異常。我們按住ctrl鍵,點開上圖中藍框部分的iinterface,檢視一下源代碼:
可以看到,在iinterface接口裡,定義了一個接口ibinder,這是ipc機制的核心接口。
再回來看iperson.java檔案的第9行定義了這樣一個抽象類:
上圖中的stub類可以比作存根。stub類繼承了binder類,同時實作了iperson接口(沒有實作iperson裡的方法)。是以進一步了解為:stub既是iperson裡的内部類,也是一個iperson。
(2)建立personimpl類,繼承iperson.stub類,重寫父類裡的方法。代碼如下:(也就是說,根據上面的java類,生成業務對象,即原理圖中b應用的業務對象)


(3)建立類myservice,代碼如下:


核心代碼:12行和35行。
因為personimpl類繼承了iperson.stub,而stub繼承了binder,binder又實作了ibinder。是以,personimpl可以了解為一個ibinder。于是可以在第35行傳回personimpl的執行個體。
(4)在清單檔案中添權重限:
現在,b應用的業務對象和服務建立好了。b應用的業務對象通過與service綁定,讓service把業務對象暴露給了a應用或者其他的應用。也就是說,service最終并沒有實作業務功能。
如果要讓a應用來通路,該怎麼做呢?
(5)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用于綁定遠端服務和取消綁定。activity_main.xml的代碼如下:


(6)mainactivity中的代碼,如下所示:


核心代碼是第38行:可以看到,這裡首先使用了myaidlservice.stub.asinterface()方法将傳入的ibinder對象傳換成了iperson對象,接下來就可以調用在iperson.aidl檔案中定義的所有接口了(41至44行)。調用之後,我們在背景列印輸出。
運作程式,點選按鈕,效果如下:
由此可見,我們确實已經成功實作跨程序通信了,在一個程序中通路到了另外一個程序中的方法。 注意,這個service是運作在主線程當中的,畢竟我們是在本地模拟的嘛。
另外注意藍色箭頭處,可以看出,這個person其實就是personimpl,因為是在本地調用。是以說,目前的跨程序通信其實并沒有什麼實質上的作用,因為這隻是在一個activity裡調用了同一個應用程式的service裡的方法。而跨程序通信的真正意義是為了讓一個應用程式去通路另一個應用程式中的service,以實作共享service的功能。那麼下面我們自然要學習一下,如何才能在其它的應用程式中調用到myservice裡的方法。
繼續回顧第(1)步中自動生成的iperson.java檔案,截取第22至32行,摘抄如下:


代碼解釋:
上方第06行的iin代表的是,查詢本地對象傳回的結果。
07行:如果iin不為空,并且iin為iperson,那就将iin強制轉換為iperson(08行)。很顯然,這裡是程序内通信(本地通信)要用到的。也就是本段中的例子。
10行:代表的是程序間通信(遠端通信),此時第07行的if語句不成立,于是傳回第10行的代理對象proxy(obj)。
實作程序間通信的兩個辦法:
如果要實作程序間通信,就必須讓mainactivity和service互相獨立。有兩個辦法:
(1)辦法一:将本地的service設定為遠端。隻需要在清單檔案中注冊service的時候将它的android:process屬性指定成:remote就可以了,代碼如下所示:
背景列印日志如下:
上圖的紅框部分顯示,service和activity并非在同一個線程内,連包名都不一樣。而iperon也并非是本地的iperon。
如果将這種方法應用到上面的第四段中,情形是這樣的:
點選button1_start_service,服務啟動。既然已經将service的android:process屬性指定成:remote,此時service和activity不在同一個線程内,那麼即使在service的onstartcommand()方法中執行耗時操作而不重新開啟子線程,程式也不會阻塞。
但是,如果點選button3_bind_service按鈕綁定服務,程式會崩潰的。這是因為,目前myservice已經是一個遠端service了,activity和service運作在兩個不同的程序當中,這時就不能再使用傳統的建立關聯的方式,程式也就崩潰了。
現在我們總結一下:
第四段中使用的是傳統的方式和service建立關聯,預設mainactivity和myservice在同一個線程内,如果将service的android:process屬性指定成:remote,此時mainactivity和myservice将在不同的線程内,但是無法綁定服務。
本段中(第五段)使用的是ipc跨程序通信,mainactivity和myservice在不同的程序中,可以綁定遠端服務。
(2)辦法二:建立另外一個工程,真正實作遠端通信。這就是我們下一段(第六段)要講的内容。
我們還是先回過頭來再鞏固一下本段中aidl的知識吧。
7、aidl支援的自定義資料類型:
我們在本段中的第4小結講到,aidl支援的類型:八大基本資料類型、string類型、charsequence、list、map、自定義,那我們就來詳細說下這個自定義資料類型。
由于這是在不同的程序之間傳遞資料,android對這類資料的格式支援是非常有限的,基本上隻能傳遞java的基本資料類型、字元串、list或map等。那麼如果我想傳遞一個自定義的類該怎麼辦呢?這就必須要讓這個類去實作parcelable接口,并且要給這個類也定義一個同名的aidl檔案進行聲明。這部分内容并不複雜,而且和service關系不大。具體操作如下:
重建立一個工程servicetest03。步驟如下:
(1)建立一個student類去實作parcelable接口。student類是作為傳遞的自定義類:


我們在這個類中放入了name和age這兩個參數,并實作了parcelable接口。注意第44行至55行代碼的修改。
接着,建立一個和類同名的aidl檔案,即建立student.aidl,代碼如下:
注意這個parcelable的第一個字母是小寫。
繼續,建立istudent.aidl,作為需要遠端傳遞的業務方法。代碼如下:


核心代碼是第03行,雖然student類檔案和本檔案是在同一個包下,但是依然要導包,否則将無法識别student類。然後在第06行代碼中,就可以把student這個類傳遞出去了。注意了,第06行傳回的是student類型,這不就是adil所支援的自定義類型嘛。
檔案結構如下:
綜上所述,傳遞自定義類,有三個步驟:
自定義類實作parcelable接口
建立同名的aidl檔案,聲明這個parcelable類型的自定義類
在需要遠端傳遞的aidl檔案中導包,引用進來
那麼接下來的步驟就和本段中的第6小節一樣了,就不再多解釋了,這裡隻貼代碼:
(2)建立studentimpl類,繼承istudent.stub類。代碼如下:(也就是說,根據步驟(1)中的java類,生成業務對象,即原理圖中b應用的業務對象)


(3)建立service類,代碼如下:


核心代碼:12行、35行、36行。
(5)在activity_main.xml中添加兩個按鈕button1_setstudent和button2_getstudent。activity_main.xml的代碼如下:


注:布局檔案裡不再添加綁定服務和取消綁定的按鈕,我們稍後在activity的生命周期裡完成這件事。


核心代碼是第44行。
我們在第75行、86至87行使用到了istudent中的業務方法。
運作程式,點選第一個按鈕,然後點選第二個按鈕,效果如下:
這樣,acitivity就成功調用了遠端service的自定義類。
六、使用bind service完成ipc程序間通信:(兩個app之間)
上一段中的跨程序通信其實并沒有什麼實質上的作用,因為這隻是在一個activity裡調用了同一個應用程式的service裡的方法。而跨程序通信的真正意義是為了讓一個應用程式去通路另一個應用程式中的service,以實作共享service的功能。那麼下面我們自然要學習一下,如何才能在其它的應用程式中調用到myservice裡的方法。
在第四段中我們已經知道,如果想要讓activity與service之間建立關聯,需要調用bindservice()方法,并将intent作為參數傳遞進去,在intent裡指定好要綁定的service,核心代碼如下:
這裡在建構intent的時候是使用myservice.class來指定要綁定哪一個service的,但是在另一個應用程式中去綁定service的時候并沒有myservice這個類,這時就必須使用到隐式intent了。
具體步驟如下:
我們在第六段中的myservice02這個工程檔案中進行修改。代碼實作如下:
(1)現在修改androidmanifest.xml中的代碼,給myservice加上一個action,如下所示:
這就說明,myservice可以響應帶有com.example.servicetest02.myservice這個action的intent。
現在重新運作一下myservice02這個程式,這樣就把遠端service端的工作全部完成了。
然後建立一個新的工程,起名為clienttest,我們就嘗試在這個程式中遠端調用myservice中的方法。
clienttest中的activity如果想要和myservice建立關聯其實也不難,首先需要将iperson.aidl檔案從servicetest02項目中拷貝過來,注意要将原有的包路徑一起拷貝過來,完成後項目的結構如下圖所示:
(2)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用于綁定遠端服務和取消綁定。activity_main.xml的代碼如下:


(3)在mainactivity中加入和遠端的myservice建立關聯的代碼,如下所示:


這部分代碼大家一定會非常眼熟吧?沒錯,這和在servicetest02的mainactivity中的代碼幾乎是完全相同的,隻是在讓activity和service建立關聯的時候我們使用了隐式intent,将intent的action指定成了com.example.servicetest02.myaidlservice(63行)。
在目前activity和myservice建立關聯之後,我們仍然是調用了setname、setage、setsex、getperson()這幾個方法,遠端的myservice會對傳入的參數進行處理并傳回結果,然後将結果列印出來。
這樣的話,clienttest中的代碼也就全部完成了,現在運作一下這個項目,然後點選bind service按鈕,此時就會去和遠端的myservice建立關聯,觀察logcat中的列印資訊如下所示:
注意紅框部分,包名是不一樣的哦。由此可見,我們确實已經成功實作跨程序通信了,在一個程式中通路到了另外一個程式中的方法。
七、messenger的使用:
介紹:messenger實作了ipc通信,底層也是使用了aidl方式。和aidl方式不同的是,messenger方式是利用handler形式處理,是以,它是線程安全的,這也表示它不支援并發處理;而aidl方式是非線程安全的,支援并發處理,是以,我們使用aidl方式時,需要保證代碼的線程安全。大部分情況下,應用中不需要并發處理,是以我們通常隻需要使用messenger方式。
過程:在程序a中建立一個message,将這個message對象通過messenger.send(message)方法傳遞到程序b的消息隊列裡,然後交給handler去處理。
當然,message對象本身是無法被傳遞到程序b的,send(message)方法會使用一個pacel對象對message對象編集,再将pacel對象傳遞到程序b中,然後解編集,得到一個和程序a中的message對象内容一樣的對象。
關于多線程的handler機制,如果不清楚的話,可以參考本人另外一篇部落格:
使用messenger來實作ipc的步驟:
在service中建立一個messenger對象并綁定一個handler
在onbind方法中通過messenger.getibinder方法傳回一個ibinder對象。
在調用的元件中的serviceconnection的onserviceconnected事件方法中根據ibinder對象來建立一個messenger對象。這樣,兩個messenger就同時綁定到一個ibinder上,進而實作通信。
在調用的元件中使用messenger的send方法來發送消息到service的messenger對象中。
那我們通過代碼來實作以下吧。建立一個全新的工程messengertest。步驟如下:
(1)建立一個messengerservice類,繼承service類,代碼如下:


核心代碼:16至28行、31行、37行。
37行中,将ibinder類型傳回之後,就已經和messenger進行綁定了。
(2)在清單檔案中注冊服務:(和activity标簽并列)
(3)修改activity_main.xml代碼,添加一個按鈕,用于發送message,代碼如下:


(4)在mainactivity作為程式的主activity,在裡面加入發送message消息和建立service連接配接的邏輯,代碼如下:


我們在上一步的messengerservice類建立了一個messenger,在這裡又建立另一個messenger(54行)。兩個messenger綁定了同一個服務,activity就可以和service實作通訊了。
點選按鈕(72行),發送消息,讓messengerservice類裡的messenger去接收,然後交給handler去處理,進而執行handlemessage()裡方法,也就是說,執行了service裡面的方法。
運作程式,點選按鈕,顯示效果如下:
說明這個messengerservice和普通service一樣,也是運作在主線程當中的。
當然了,這裡的messenger的實作比較簡單,如果以後需要實作複雜ipc通路,還是需要自己去寫aidl才更加直接有效,可控性強。