http://www.cnblogs.com/freeliver54/archive/2012/06/13/2547765.htmlhtml
簡述了Service的一些基礎知識以及Service和Thread的簡單區別,本文將着重講解與Service交互的五種基本方式:廣播交互、共享文件交互、Mssenger(信使)交互、自定義接口交互、AIDL交互。java
1. 廣播交互android
提到Activity與Service的交互,可能狠多人首先想到的就是BroadCast——廣播。在Android中,廣播是系統提供的一種很好的交互方式。好比:在電池電量太低,開機完成等狀況下,系統都會發出相應的系統廣播,咱們的應用程序只須要註冊相應的廣播接收器,就能夠接收到這些系統的廣播。同時,咱們也能夠定義本身的廣播,這樣在不一樣的Activity、Service以及應用程序之間,就能夠經過廣播來實現交互。咱們經過模擬應用程序後臺下載的狀況來分析Service與Activity的交互方式。實現效果如圖1.1:服務器
圖1.1併發
當咱們點擊StartService按鈕以後,界面上的進度條將會每隔一秒加1。由於是模擬下載,所以下載動做咱們在Service中經過一個Timer定時器來實現,在Timer中對一個整型數據i進行自加(i++),而後Client端獲取Server端的i值並顯示在界面上,從而達到模擬的目的。app
1.1. 實現原理框架
Server端將目前的下載進度,經過廣播的方式發送出來,Client端註冊此廣播的監聽器,當獲取到該廣播後,將廣播中當前的下載進度解析出來並更新到界面上。ide
1.2. 實現步驟this
1.2.1 在Client端中經過startService()啟動Service。spa
這裏的mIntent = new Intent();Process.myPid()方法能夠獲取當前進程的ID號。
1.2.2 DownLoadService接到啓動的命令以後,執行onCreate()方法,並在其中開啓timer計數模擬下載。
這裏的intent是Server端向Client端傳送數據用的,使用的action是」com.seven.broadcast」,Client端只有註冊了相應action纔可以接收到Server端的廣播,並解析其中的內容。Process.myPid()是獲取當前進程的ID。
1.2.3 在Server端的timer計數其中發送廣播,告知Client端目前下載進度。
經過intent.putExtra(key,value);設置intent的值,而後經過sendBroadcast(intent)2方法,將廣播發送出去。
1.2.4 在Client端經過匿名內部類的方式實例化BroadcastReceiver並覆寫其中的onReceive()方法。
在onReceive()方法中,判斷是否爲Server端發送的廣播,若是是則對廣播中攜帶的intent數據進行解包處理。這裡也能夠單獨寫一個類繼承自BroadcastReceiver,在其中覆寫onReceive()方法,在Client端中實例化其對象,一樣能夠達到相應的效果,這樣作能夠爲後面實現靜態註冊廣播。
1.2.5 更新主介面下載進度。
這裏對獲取到的進度進行了一次判斷,若是獲取到的值沒有異常,那麼將會顯示到界面,並更新進度條的進度,若是異常則返回。
1.2.6 必定要對Broadcast進行註冊和取消註冊。只有註冊以後相應的broadcast以後才能接收到廣播註冊方法有兩種。
動態註冊/取消註冊:
動態註冊能夠隨時註冊隨時取消。
靜態註冊:
注:這裏的MyBroadcastReceiver是一個繼承自BroadcastReceiver的類。靜態註冊只要註冊了一次那麼只要該程序沒有被卸載那麼該廣播將一直有效。
最後貼出整個AndroidManifest.xml文件
這裏的android:process =」:remote」可使該Service運行在單獨進程中,從而能夠模擬跨進程通訊。
1.3 小結
經過廣播的方式實現Activity與Service的交互操做簡單且容易實現,能夠勝任簡單級的應用。但缺點也十分明顯,發送廣播受到系統制約。系統會優先發送系統級廣播,在某些特定的狀況下,咱們自定義的廣播可能會延遲。同時在廣播接收器中不能處理長耗時操做,不然系統會出現ANR即應用程序無響應。
2. 共享文件交互
這裏提到的共享文件指的是Activity和Service使用同一個文件來達到傳遞數據的目的。咱們使用SharedPreferences來實現共享,固然也可使用其它IO方法實現,經過這種方式實現交互時須要注意,對於文件的讀寫的時候,同一時間只能一方讀一方寫,不能兩方同時寫。實現效果如圖2.1:
圖2.1
2.1 實現原理
Server端將當前下載進度寫入共享文件中,Client端經過讀取共享文件中的下載進度,並更新到主界面上。
2.2 實現步驟
2.2.1 在Client端經過startService()啟動Service。
這裏的intent = new Intent()2只是爲了啓動Server端。
2.2.2 Server端收到啓動intent以後執行onCreate()方法,並開啓timer,模擬下載,以及初始化SharedPreferences對象preferences。
經過preferences=getSharedPreferences(String,MODE)2能夠在/data/data/com.seven.servicetestdemo/shared_prefs文件夾下創建相應的xml文件。
2.2.3 開始計數並將下載進度寫入shared_prefs文件夾下的xml文件中,內容以鍵值對的方式保存。
對於SharedPreferences的使用須要注意一下幾點:
首先,使用sharedPreferences前須要獲取文件引用。
preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);
其次,使用sharedpreferences寫數據方式。
preferences.edit().putInt("CurrentLoading", i).commit();
最後,讀取數據的方式。
int couLoad = preferences.getInt("CurrentLoading", 0);
2.2.4 Client端經過讀取/data/data/com.seven.servicetestdemo/shared_prefs文件夾下的xml文件,並取得裏面的鍵值對,從而獲取到當前的下載進度,並更新到主界面上。
2.3 小結
因為方法簡單,所以就不貼出AndroidManifest.xml文件了。對於這種方式實現Activity與Service的交互,能夠說很方便,就像使用管道,一個往裡寫,一個往外讀。但這種方式也有缺陷,寫入數據較爲複雜以及數據量較大時,就有可能致使寫入與讀數據出不一致的錯誤。同時由於通過了一箇中轉站,這種操做將更耗時。
3. Messenger交互(信使交互)
Messenger翻譯過來指的是信使,它引用了一個Handler對象,別人可以向它發送消息(使用mMessenger.send(Message msg)方法)。該類容許跨進程間基於Message通訊,在服務端使用Handler建立一個 Messenger,客戶端只要得到這個服務端的Messenger對象就能夠與服務端通訊了。也就是說咱們能夠把Messenger當作Client端與Server端的傳話筒,這樣就能夠溝通交流了。實現效果如圖3.1:
圖3.1
3.1 實現原理
在Server端與Client端之間經過一個Messenger對象來傳遞消息,該對象相似於信息中轉站,全部信息經過該對象攜帶。
3.2 Messenger的通常用法
(1). 在Server端建立信使對象。
mMessenger = new Messenger(mHandler)
(2). Client端使用bindService()綁定Server端。
(3). Server端的onBind()方法返回一個binder對象。
return mMessenger.getBinder();
(4). Client端使用返回的binder對象獲得Server端信使。
這裏雖然是new了一個Messenger,但咱們查看它的實現
發現它的mTarget是經過AIDL獲得的,實際上就是遠程建立的那個。
(5). Client端可使用這個Server端的信使對象向Server端發送消息。
rMessenger.send(msg);
這樣Server端的Handler對象就能收到消息了,而後能夠在其handlerMessage(Message msg)方法中進行處理。通過這5個步驟以後只有Client端向Server端發送消息,這樣的消息傳遞是單向的,那麼如何實現消息的雙向傳遞呢?
首先須要在第5步作修改,在send(msg)前經過msm.replyTo = mMessenger將Client端本身的信使設置到消息中,這樣Server端接收到消息時同時也獲得了Client端的信使對象,而後Server端也能夠經過使用獲得的Client端的信使對象來項Client端發送消息 cMessenger = msg.replyTo2 cMessenger.send(message);
這樣即完成了從Server端向Client端發送消息的功能,這樣Client端能夠在本身的Handler對象的handlerMessage()方法中接收服務端發送來的message進行處理。
3.3 實現步驟
3.3.1 建立並初始化Server端的信使對象。
3.3.2 在Client端使用bindService()方法綁定Server端。
3.3.3 在Server端的onBind()方法中返回一個binder對象。
這裡的mMessenger就是Server端的信使對象。
3.3.4 Client端使用ServiceConnected()方法來獲取Server端的信使對象。
獲取Server端的信使對象的同時,也初始化Client端的本身的信使對象,而且經過sendMessage()方法發送消息給Server端,表示能夠開始下載了。
3.3.5 Client端使用獲取到的rMessenger來發送消息給Server端,同時將Client端的信使封裝到消息中,一併發送給Server端。
這裏的MessengerService.TEST為Server端裏的一個靜態常量。Msg.replyTo=mMessenger;表示發送給Server端的信息裏攜帶Client端的信使。
3.3.6 Server端獲取Client端發送的消息並獲得Client端的信使對象。
在接收到Client端的信息以後,Server端開啟timer模擬下載,並接收Client端的信使對象。
3.3.7 Server端向Client端發送數據。
直接使用接收到的Client端的信使對象來發送當前下載進度給Client端。
3.3.8 Client端接收來自Server端的數據。
Client端的接收和Server端的接收狠相似。接收到Server端傳過來的數據以後進行介面更新,以及下載進度更新。
如下是AndroidManifest.xml文件:
這裏在Service的註冊中加入了過濾動做,只有相匹配的action才能啓動相應的Service。
3.4 小結
經過Messenger來實現Activity和Service的交互,稍微深刻一點咱們就能夠知道,其實Messenger也是經過AIDL來實現的。對於前兩種實現方式,Messenger方式整體上來說也是比較容易理解的,這就和平時使用Handler和Thread通訊一個道理。
4. 自定義接口交互
何謂自定義接口呢,其實就是咱們本身經過接口的實現來達到Activity與Service交互的目的,咱們經過在Activity和Service之間架設一座橋樑,從而達到數據交互的目的,而這種實現方式和AIDL很是相似(後文會說到)。實現效果如圖4.1:
圖4.1
4.1 實現原理
自定義一個接口,該接口中有一個獲取當前下載進度的空方法。Server端用一個類繼承自Binder並實現該接口,覆寫了其中獲取當前下載進度的方法。Client端經過ServiceConnection獲取到該類的對象,從而可以使用該獲取當前下載進度的方法,最終實現實時交互。
4.2 實現步驟
4.2.1 新建一個Interface,並在其中建立一個用於獲取當前下載進度的的空方法getCurrentLoad()。
4.2.2 新建Server端DownService實現ICountService並在其中經過一個內部類ServiceBinder繼承自Binder並實現ICoutService接口。
在Server端中,實現獲取下載進度的空方法getCurrentLoad();這是Eclipse自動生成的,重點不在這裡。咱們須要在ServiceBinder類中覆寫getCurrentLoad()方法,這裡咱們返回當前的下載進度i。
4.2.3 Client端使用bindService()綁定Server端。
在Client端綁定Server端的同時,延遲1s開始獲取下載進度。其中的intent = new Intent(「com.seven.test」)2com.seven.test該字符串要與在AndroidManifest.xml中申明的一致。
4.2.4 Server端返回binder對象。
這裏的serviceBinder由於繼承了Binder所以也是Binder對象。
4.2.5 Client端經過ServiceConnection來獲取Server端的binder對象。
獲取的過程是在bindService()過程當中完成的,這裏的iCountService是接口ICountService的對象,在這裏獲得實例化。
4.2.6 在綁定完成以後,Server端會開啓下載,在實際狀況中Server端會開啓獨立線程用於下載,這裏用i++來代替。
bindService()方法執行以後會調用DownLoadService中的onCreate()方法,在其onCreate()方法中開啓timer使得i++。
4.2.7 Server端已經開啓了下載,那麼Client端須要及時獲取下載進度並在主界面上更新。
Client端的Timer在bindService()完成以後1秒再開始獲取下載進度,獲取方法是直接經過int curLoad = iCountService.getCurrentLoad();這裏的getCurrentLoad()方法是DownLoadService內部類ServiceBinder中的方法。Client端將獲取到的下載進度更新到介面上並更新進度條。
4.3 小結
經過上面的例子能夠知道,這種方法簡單實用,擴展性強,但其也有一些缺點,好比須要延遲一些再開始獲取Server端的數據,從而沒法徹底實現從零開始同步更新。綜其所述,經過自定義接口實現Activity與Service交互的方法仍是比較實用的。適用於同進程中通訊,不能進行跨進程通訊。
5. AIDL交互
什麼是AIDL?
AIDL是Android Interface Definition Language的首字母縮寫, 也就是Android接口定義語言。說起AIDL就不得不說下Android的服務,Android 支持兩種服務類型的服務即本地服務和遠程服務。
本地服務沒法供在設備上運行的其餘應用程序訪問,也就是說只能該應用程序內部調用,好比某些應用程序中的下載類服務,這些服務只能由內部調用。而對於遠程服務,除了能夠由本應用程序調用,還能夠容許其餘應用程序訪問。遠程服務通常經過AIDL來實現,能夠進行進程間通訊,這種服務也就是遠程服務。
本地服務與遠程服務仍是有一些重要的區別。具體來說,若是服務徹底只供同一進程中的組件使用(運行後臺任務),客戶端一邊經過調用 Context.startService()來啓動該服務。這種類型的服務爲本地服務,它的通常用途是後臺執行長耗時操做。而遠程服務通常經過bindService()方法啓動,主要爲不一樣進程間通訊。咱們也將遠程服務稱爲AIDL支持服務,由於客戶端使用 AIDL 與服務通訊。Android中對於遠程服務有多種叫法:遠程服務、AIDL服務、外部服務和RPC服務。
5.1 AIDL實現流程圖
圖5.1
這屬於代理/存根結構,經過這張AIDL的流程圖,很容易發現Android實現IPC實際上是在原來的C/S框架上加入了代理/存根結構。
好比,你到自動取款機上去取款。那麼你就是客戶(Client),取款機就是你的代理(Proxy);你不會在意錢具體放在那裏,你只想將你的錢從取款機中取出來。你同銀行之間的操做徹底是取款機代理實現。你的取款請求經過取款機傳到另外一邊,即銀行的服務器(Server)。它也沒有必要知道你在哪兒取錢,它所關心的是你的身份和你取款多少。當它確認你的權限,就進行相應的操做,返回操做結果給取款機,取款機根據服務器返回結果,從保險櫃裏取出相應數量的錢給你。你取出卡後,操做完成。取款機不是直接同服務器鏈接的,他們之間還有一個「存根(Stub)」,取款機與存根通訊,服務器與存根通訊,從某種意義上說存根就是服務器的代理。實現效果如圖5.2:
圖5.2
5.3 實現原理
AIDL屬於Android的IPC機制,經常使用於跨進程通訊,主要實現原理基於底層Binder機制。
5.4 實現步驟
5.4.1 創建工程。按照圖5.3和圖5.4創建AIDLServer端以及AIDLClient端。在AIDLServer端中只有一個服務程序,沒有主界面,其主要功能就是負責下載。AIDLClient端從AIDLServer端獲取當前下載進度(注:AIDLServer端和AIDLClient端是不一樣的兩個APK,在模擬本例的時候,須要先在模擬器上安裝AIDLServer編譯出來的APK,安裝方法能夠直接在模擬器上運行一次,能夠經過adb install your.apk 來安裝)。
圖5.3
AIDLServer端中新建了一個ICountService.aidl的文件,該文件內容以下:
aidl文件的書寫規範以下:
(1). Android支持String和CharSequence(以及Java的基本數據類型);
(2). 若是須要在aidl中使用其它aidl接口類型,須要import,即便是在相同包結構下;
(3). Android容許傳遞實現Parcelable接口的類,須要import;
(4). Android支持集合接口類型List和Map,可是有一些限制,元素必須是基本型或者前面三種狀況,不須要import集合接口類,可是須要對元素涉及到的類型import;
(5). 非基本數據類型,也不是String和CharSequence類型的,須要有方向指示,包括in、out和inout,in表示由客戶端設置,out表示由服務端設置,inout是二者都可設置。
圖5.4
AIDLClient端須要將AIDLServer端的ICountService.aidl文件復製過去,這裡爲了方便,新建了一個和Server端同名的包,並將ICountService.aidl放與其中。
5.4.2 咱們在Server端創建好ICoutService.aidl文件以後,Eclipse會在/gen/com.seven.aidlserver/目錄下自動生成ICountService.java文件。該文件由Eclipse自動生成,請勿隨便修改,後文咱們需引用到的內容以下:
5.4.3 在Server端新建一個內部類繼承自ICountService.Stub並覆寫其中的getCount()方法,以及實例化該類的一個對象serviceBinder。
這裏與前面提到的「經過接口實現交互」很是相似。
5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder對象。
5.4.5 在Server端的onCreate()方法中,開啓timer,模擬下載。在Client端經過bindService()綁定Server端的時候,會首先執行Server端的onCreate()方法。
5.4.6 Client端經過bindService()綁定Server端。
這裏的intent = new Intent(「com.seven.aidlserver」);這裏跟Server端註冊Service時過濾的要一致,也就是說只有發出相同的action纔會啓動該Service。同時開啓了一個timer用於獲取下載進度。
5.4.7 Client端經過ServiceConnection來獲取Server端的binder對象。
這裏的iCountService對象實際上就是ICountService的對象在此實例化。
5.4.8 獲取當前下載進度並更新到界面上。
經過更新介面上的進度條,能夠狠容易的後去當前下載進度。因為AIDLServer端只是一個繼承自Service的服務,所以就不貼出其AndroidManifest.xml文件了。
5.5 小結
AIDL在Android中是進程間通訊經常使用的方式,可能使用較為複雜,但效率高,擴展性好。同時不少系統服務就是以這種方式完成與應用程序通訊的。
本文經過五個例子,分別介紹了五種與Service交互的方法,這些方法有的簡單,有的可能要複雜一些。在這裏只是作爲對Servie的一些總結。後文附上源碼下載連接,不須要積分的哦。:D
全部源碼均在Ubuntu 10.04 Eclipse-Indigo下實驗經過 模擬器採用的是2.3的鏡像