Android中程式與Service互動的方式——互動方式

lostinai發表於2013-06-03
 上一篇文章:Android中程式與Service互動的方式——綜述 簡述了Service的一些基礎知識以及Service和Thread的簡單區別,本文將著重講解與Service互動的五種基本方式:廣播互動、共享檔案互動、Mssenger(信使)互動、自定義介面互動、AIDL互動。

       1. 廣播互動

       提到Activity與Service的互動,可能狠多人首先想到的就是BroadCast——廣播。在Android中,廣播是系統提供的一種很好的互動方式。比如:在電池電量過低,開機完成等情況下,系統都會發出相應的系統廣播,我們的應用程式只需要註冊相應的廣播接收器,就可以接收到這些系統的廣播。同時,我們也可以定義自己的廣播,這樣在不同的Activity、Service以及應用程式之間,就可以通過廣播來實現互動。我們通過模擬應用程式後臺下載的情況來分析Service與Activity的互動方式。實現效果如圖1.1:

圖1.1

        當我們點選StartService按鈕之後,介面上的進度條將會每隔一秒加1。因為是模擬下載,因此下載動作我們在Service中通過一個Timer定時器來實現,在Timer中對一個整型資料i進行自加(i++),然後Client端獲取Server端的i值並顯示在介面上,從而達到模擬的目的。

        1.1. 實現原理

        Server端將目前的下載進度,通過廣播的方式傳送出來,Client端註冊此廣播的監聽器,當獲取到該廣播後,將廣播中當前的下載進度解析出來並更新到介面上。

        1.2. 實現步驟

        1.2.1 在Client端中通過startService()啟動Service。

[java] view plaincopy
  1. if(v == startBtn){  
  2.     Log.i(TAG, "start button clicked...pid: "+Process.myPid());  
  3.     mIntent.setClass(BroadCastService.this, DownLoadService.class);  
  4.     startService(mIntent);  
  5. }  
          這裡的mIntent = new Intent();Process.myPid()方法可以獲取當前程式的ID號。
       1.2.2 DownLoadService接到啟動的命令之後,執行onCreate()方法,並在其中開啟timer計數模擬下載。

[java] view plaincopy
  1. @Override  
  2. public void onCreate() {  
  3.     super.onCreate();  
  4.     Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());  
  5.     intent = new Intent("com.seven.broadcast");  
  6.     mTimer = new Timer();  
  7.     mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);  
  8. }  
          這裡的intent是Server端向Client端傳送資料用的,使用的action是”com.seven.broadcast”,Client端只有註冊了相應action才能夠接收到Server端的廣播,並解析其中的內容。Process.myPid()是獲取當前程式的ID。
        1.2.3 在Server端的timer計數其中傳送廣播,告知Client端目前下載進度。

[java] view plaincopy
  1. class MyTimerTask extends TimerTask{  
  2.     @Override  
  3.     public void run() {  
  4.         if(i==100){  
  5.             i=0;  
  6.         }  
  7.         intent.putExtra("CurrentLoading", i);  
  8.         sendBroadcast(intent);  
  9.         i++;  
  10.         Log.e(TAG, "i= "+i);  
  11.     }  
  12. }  
          通過intent.putExtra(key,value);設定intent的值,然後通過sendBroadcast(intent)2方法,將廣播傳送出去。

       1.2.4 在Client端通過匿名內部類的方式例項化BroadcastReceiver並覆寫其中的onReceive()方法。

[java] view plaincopy
  1. BroadcastReceiver receiver = new BroadcastReceiver() {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         if(MYACTION.equals(intent.getAction())){  
  5.             Log.i(TAG, "get the broadcast from DownLoadService...");  
  6.             curLoad = intent.getIntExtra("CurrentLoading", ERROR);  
  7.             mHandler.sendMessage(mHandler.obtainMessage());  
  8.         }  
  9.     }  
  10. };  
          在onReceive()方法中,判斷是否為Server端傳送的廣播,如果是則對廣播中攜帶的intent資料進行解包處理。這裡也可以單獨寫一個類繼承自BroadcastReceiver,在其中覆寫onReceive()方法,在Client端中例項化其物件,同樣可以達到相應的效果,這樣做可以為後面實現靜態註冊廣播。
        1.2.5 更新主介面下載進度。

[java] view plaincopy
  1. Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         Log.i(TAG, "current loading: "+curLoad);  
  6.         if(curLoad<0||curLoad>100){  
  7.             Log.e(TAG, "ERROR: "+curLoad);  
  8.             return;  
  9.         }  
  10.         mProgressBar.setProgress(curLoad);  
  11.         mTextView.setText(curLoad+"%");  
  12.     }  
  13. };  
             這裡對獲取到的進度進行了一次判斷,如果獲取到的值沒有異常,那麼將會顯示到介面,並更新進度條的進度,如果異常則返回。
         1.2.6 一定要對Broadcast進行註冊和取消註冊。只有註冊之後相應的broadcast之後才能接收到廣播註冊方法有兩種。
         動態註冊/取消註冊:

[java] view plaincopy
  1. @Override  
  2. protected void onResume() {  
  3.     super.onResume();  
  4.     Log.i(TAG, "register the broadcast receiver...");  
  5.     IntentFilter filter = new IntentFilter();  
  6.     filter.addAction(MYACTION);  
  7.     registerReceiver(receiver, filter);  
  8. }  
  9. @Override  
  10. protected void onDestroy() {  
  11.     super.onDestroy();  
  12.     Log.i(TAG, "unregister the broadcast receiver...");  
  13.     unregisterReceiver(receiver);  
  14. }  
           動態註冊可以隨時註冊隨時取消。

        靜態註冊:

[html] view plaincopy
  1. <receiver android:name="MyBroadcastReceiver">  
  2.     <intent-filter>  
  3.         <action android:name="com.seven.broadcast" />  
  4.     </intent-filter>  
  5. </receiver>  
           注:這裡的MyBroadcastReceiver是一個繼承自BroadcastReceiver的類。靜態註冊只要註冊了一次那麼只要該程式沒有被解除安裝那麼該廣播將一直有效。

        最後貼出整個AndroidManifest.xml檔案

[html] view plaincopy
  1. <application android:icon="@drawable/icon" android:label="@string/app_name">  
  2.     <activity android:name=".BroadCastService"  
  3.               android:label="@string/app_name">  
  4.         <intent-filter>  
  5.             <action android:name="android.intent.action.MAIN" />  
  6.             <category android:name="android.intent.category.LAUNCHER" />  
  7.         </intent-filter>  
  8.     </activity>  
  9.     <service android:name="DownLoadService" android:process=":remote"/>  
  10. </application>  
          這裡的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。

[java] view plaincopy
  1. if(startSerBtn==v){  
  2.     Log.i(TAG, "Start Button Clicked.");  
  3.     if(intent!=null){  
  4.     startService(intent);  
  5.     timer.schedule(new MyTimerTask(), 0, TIME * 1000);  
  6.     }  
  7. }  
          這裡的intent = new Intent()2只是為了啟動Server端。
        2.2.2 Server端收到啟動intent之後執行onCreate()方法,並開啟timer,模擬下載,以及初始化SharedPreferences物件preferences。

[java] view plaincopy
  1. @Override  
  2. public void onCreate() {  
  3.     super.onCreate();  
  4.     Log.i(TAG, "DownLoadService.onCreate()...");  
  5.     preferences = getSharedPreferences("CurrentLoading_SharedPs"0);  
  6.     timer = new Timer();  
  7.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
  8. }  
          通過preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs資料夾下建立相應的xml檔案。

       2.2.3 開始計數並將下載進度寫入shared_prefs資料夾下的xml檔案中,內容以鍵值對的方式儲存。

[java] view plaincopy
  1. class MyTimerTask extends TimerTask{  
  2.     @Override  
  3.     public void run() {  
  4.         setCurrentLoading();  
  5.         if(100==i){  
  6.             i=0;  
  7.         }  
  8.         i++;  
  9.     }         
  10. }     
  11. private void setCurrentLoading() {  
  12.     preferences.edit().putInt("CurrentLoading", i).commit();  
  13. }  
           對於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檔案,並取得裡面的鍵值對,從而獲取到當前的下載進度,並更新到主介面上。

[java] view plaincopy
  1. Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         int couLoad = preferences.getInt("CurrentLoading"0);  
  6.         mProgressBar.setProgress(couLoad);  
  7.         currentTv.setText(couLoad+"%");  
  8.     }  
  9.  };  

          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端信使。

[java] view plaincopy
  1. public void onServiceConnected(ComponentName name, IBinder service) {    
  2.               rMessenger = new Messenger(service);        
  3.              ......  
  4.  }  
          這裡雖然是new了一個Messenger,但我們檢視它的實現

[java] view plaincopy
  1. public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);  }   
           發現它的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端的信使物件。

[java] view plaincopy
  1. private Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         switch (msg.what) {  
  6.         case TEST:  
  7.             Log.e(TAG, "Get Message from MainActivity.");  
  8.             cMessenger = msg.replyTo;  
  9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
  10.             break;  
  11.             default:  
  12.                 break;  
  13.             }  
  14.         }         
  15. };  
  16. //It's the messenger of server  
  17. private Messenger mMessenger = new Messenger(mHandler);  

          3.3.2 在Client端使用bindService()方法繫結Server端。

[java] view plaincopy
  1. private void doBindService(){  
  2.         Log.i(TAG, "doBindService()...");  
  3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true  
  4.         Log.e(TAG, "Is bind: "+mIsBind);  
  5. }  

          3.3.3 在Server端的onBind()方法中返回一個binder物件。

[java] view plaincopy
  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "MessengerService.onBind()...");  
  4.     return mMessenger.getBinder();  
  5. }  
          這裡的mMessenger就是Server端的信使物件。

        3.3.4 Client端使用ServiceConnected()方法來獲取Server端的信使物件。

[java] view plaincopy
  1. private ServiceConnection serConn = new ServiceConnection() {     
  2.     @Override  
  3.     public void onServiceDisconnected(ComponentName name) {  
  4.         Log.i(TAG, "onServiceDisconnected()...");  
  5.         rMessenger = null;  
  6.     }         
  7.     @Override  
  8.     public void onServiceConnected(ComponentName name, IBinder service) {  
  9.         Log.i(TAG, "onServiceConnected()...");  
  10.     rMessenger = new Messenger(service);//get the object of remote service  
  11.     mMessenger = new Messenger(mHandler);//initial the object of local service  
  12.     sendMessage();  
  13.     }  
  14. };  
           獲取Server端的信使物件的同時,也初始化Client端的自己的信使物件,並且通過sendMessage()方法傳送訊息給Server端,表示可以開始下載了。

        3.3.5 Client端使用獲取到的rMessenger來傳送訊息給Server端,同時將Client端的信使封裝到訊息中,一併傳送給Server端。

[java] view plaincopy
  1. private void sendMessage() {  
  2.     Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0  
  3.     msg.replyTo = mMessenger;  
  4.     try {  
  5.         rMessenger.send(msg);  
  6.     } catch (RemoteException e) {  
  7.         e.printStackTrace();  
  8.     }  
  9. }  
           這裡的MessengerService.TEST為Server端裡的一個靜態常量。Msg.replyTo=mMessenger;表示傳送給Server端的資訊裡攜帶Client端的信使。

        3.3.6 Server端獲取Client端傳送的訊息並得到Client端的信使物件。

[java] view plaincopy
  1. private Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         switch (msg.what) {  
  6.         case TEST:  
  7.             Log.e(TAG, "Get Message from MainActivity.");  
  8.             cMessenger = msg.replyTo;//get the messenger of client  
  9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
  10.             break;  
  11.         default:  
  12.             break;  
  13.         }  
  14.     }  
  15. };  
           在接收到Client端的資訊之後,Server端開啟timer模擬下載,並接收Client端的信使物件。

        3.3.7 Server端向Client端傳送資料。

[java] view plaincopy
  1. class MyTimerTask extends TimerTask {  
  2.     @Override  
  3.     public void run() {  
  4.         if (i == 100) {  
  5.             i = 0;  
  6.         }  
  7.         try {  
  8.             //send the message to the client  
  9.         Message message = Message.obtain(null, MessengerService.TEST,i, 0);  
  10.             cMessenger.send(message);  
  11.         } catch (RemoteException e) {  
  12.                 e.printStackTrace();  
  13.         }  
  14.             i++;  
  15.     }  
  16. }  
          直接使用接收到的Client端的信使物件來傳送當前下載進度給Client端。

        3.3.8 Client端接收來自Server端的資料。


[java] view plaincopy
  1. private Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         switch (msg.what) {  
  6.         case MessengerService.TEST:  
  7.             Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);  
  8.             int curLoad = msg.arg1;  
  9.             mTextView.setText(curLoad+"%");  
  10.             mProgressBar.setProgress(curLoad);  
  11.             break;  
  12.         default:  
  13.             break;  
  14.         }  
  15.     }  
  16. };  
           Client端的接收和Server端的接收狠類似。接收到Server端傳過來的資料之後進行介面更新,以及下載進度更新。

        以下是AndroidManifest.xml檔案:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.       package="com.seven.messengerservicedemo"  
  4.       android:versionCode="1"  
  5.       android:versionName="1.0">  
  6.     <uses-sdk android:minSdkVersion="10" />  
  7.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
  8.         <activity android:name=".MainActivity"  
  9.                   android:label="@string/app_name">  
  10.             <intent-filter>  
  11.                 <action android:name="android.intent.action.MAIN" />  
  12.                 <category android:name="android.intent.category.LAUNCHER" />  
  13.             </intent-filter>  
  14.         </activity>  
  15.     <service android:name="MessengerService">  
  16.         <intent-filter>  
  17.     <action ndroid:name="com.seven.messagerservice.MessengerService" />  
  18.         </intent-filter>  
  19.     </service>  
  20. </application>  
  21. </manifest>  
           這裡在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()。

[java] view plaincopy
  1. package com.seven.servicetestdemo;  
  2.   
  3. public interface ICountService {  
  4.     public int getCurrentLoad();  
  5. }  

        4.2.2 新建Server端DownService實現ICountService並在其中通過一個內部類ServiceBinder繼承自Binder並實現ICoutService介面。

[java] view plaincopy
  1. public class DownLoadService extends Service implements ICountService{  
  2. private ServiceBinder serviceBinder = new ServiceBinder();    
  3. public class ServiceBinder extends Binder implements ICountService{  
  4.     @Override  
  5.     public int getCurrentLoad() {  
  6.         Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);  
  7.         return i;  
  8.     }     
  9. }  
  10. @Override  
  11. public int getCurrentLoad() {  
  12.     return 0;  
  13. }  
  14. }  
          在Server端中,實現獲取下載進度的空方法getCurrentLoad();這是Eclipse自動生成的,重點不在這裡。我們需要在ServiceBinder類中覆寫getCurrentLoad()方法,這裡我們返回當前的下載進度i。

       4.2.3 Client端使用bindService()繫結Server端。

[java] view plaincopy
  1. if (startSerBtn == v) {  
  2.     Log.i(TAG, "Start Button Clicked.");  
  3.     bindService(intent, serConn, BIND_AUTO_CREATE);  
  4.     timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//這裡一定要延遲一下再開始獲取資料,不然會報空指標異常  
  5. }  
           在Client端繫結Server端的同時,延遲1s開始獲取下載進度。其中的intent = new Intent(“com.seven.test”)2com.seven.test該字串要與在AndroidManifest.xml中申明的一致。

       4.2.4 Server端返回binder物件。

[java] view plaincopy
  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "DownLoadService.onBind()...");  
  4.     return serviceBinder;  
  5. }  
           這裡的serviceBinder因為繼承了Binder因此也是Binder物件。

        4.2.5 Client端通過ServiceConnection來獲取Server端的binder物件。

[java] view plaincopy
  1. private ServiceConnection serConn = new ServiceConnection() {  
  2. @Override  
  3.     public void onServiceDisconnected(ComponentName name) {  
  4.         iCountService = null;  
  5.     }         
  6.     @Override  
  7.     public void onServiceConnected(ComponentName name, IBinder service) {  
  8.         Log.i(TAG, "onServiceConnected()...");  
  9.         iCountService = (ICountService)service;  
  10.     }  
  11. };  
          獲取的過程是在bindService()過程中完成的,這裡的iCountService是介面ICountService的物件,在這裡得到例項化。

        4.2.6 在繫結完成之後,Server端會開啟下載,在實際情況中Server端會開啟獨立執行緒用於下載,這裡用i++來代替。

[java] view plaincopy
  1. @Override  
  2. public void onCreate() {  
  3.     super.onCreate();  
  4.     Log.i(TAG, "DownLoadService.onCreate()...");  
  5.     timer = new Timer();  
  6.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
  7. }  
  8. class MyTimerTask extends TimerTask{  
  9.     @Override  
  10.     public void run() {  
  11.         if(100==i){  
  12.             i=0;  
  13.         }  
  14.         i++;  
  15.     }  
  16. }  
          bindService()方法執行之後會呼叫DownLoadService中的onCreate()方法,在其onCreate()方法中開啟timer使得i++。

        4.2.7 Server端已經開啟了下載,那麼Client端需要及時獲取下載進度並在主介面上更新。

[java] view plaincopy
  1. Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         Log.i(TAG, "handleMessage...");  
  6.         int curLoad = iCountService.getCurrentLoad();  
  7.         mProgressBar.setProgress(curLoad);  
  8.         currentTv.setText(curLoad+"%");  
  9.     }  
  10.  };  
  11. class MyTimerTask extends TimerTask{  
  12.     @Override  
  13.     public void run() {  
  14.         mHandler.sendMessage(mHandler.obtainMessage());  
  15.     }  
  16. }  
           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的檔案,該檔案內容如下:

[plain] view plaincopy
  1. package com.seven.aidlserver;  
  2.   
  3. interface ICountService{  
  4.     int getCount();  
  5. }  

          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自動生成,請勿隨便修改,後文我們需引用到的內容如下:

[java] view plaincopy
  1. public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {  
  2.     if ((obj == null)) {  
  3.         return null;  
  4.     }  
  5. android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);  
  6.     if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {  
  7.         return ((com.seven.aidlserver.ICountService) iin);  
  8.     }  
  9.     return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);  
  10. }  

          5.4.3 在Server端新建一個內部類繼承自ICountService.Stub並覆寫其中的getCount()方法,以及例項化該類的一個物件serviceBinder。

[java] view plaincopy
  1. private AIDLServerBinder serviceBinder = new AIDLServerBinder();  
  2. class AIDLServerBinder extends ICountService.Stub{  
  3.     @Override  
  4.     public int getCount() throws RemoteException {  
  5.         return i;  
  6.     }  
  7. }  
         這裡與前面提到的“通過介面實現互動”非常類似。

        5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder物件。

[java] view plaincopy
  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "AIDLServer.onBind()...");  
  4.     return serviceBinder;  
  5. }  

         5.4.5 在Server端的onCreate()方法中,開啟timer,模擬下載。在Client端通過bindService()繫結Server端的時候,會首先執行Server端的onCreate()方法。


[java] view plaincopy
  1. @Override  
  2. public void onCreate() {  
  3.     super.onCreate();  
  4.     Log.i(TAG, "AIDLServer.onCreate()...");  
  5.     mTimer = new Timer();  
  6.     mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);  
  7. }  
  8. class MyTimerTask extends TimerTask{  
  9.     @Override  
  10.     public void run() {  
  11.         if(i==100){  
  12.             i=0;  
  13.         }  
  14.         i++;  
  15.     }  
  16. }  

          5.4.6 Client端通過bindService()繫結Server端。


[java] view plaincopy
  1. if(startBtn==v){  
  2.     Log.i(TAG, "start button click.");  
  3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);  
  4.     mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);  
  5. }  
          這裡的intent = new Intent(“com.seven.aidlserver”);這裡跟Server端註冊Service時過濾的要一致,也就是說只有發出相同的action才會啟動該Service。同時開啟了一個timer用於獲取下載進度。
        5.4.7 Client端通過ServiceConnection來獲取Server端的binder物件。

[java] view plaincopy
  1. private ServiceConnection serConn = new ServiceConnection() {         
  2.     @Override  
  3.     public void onServiceDisconnected(ComponentName name) {  
  4.         iCountService = null;  
  5.     }         
  6.     @Override  
  7.     public void onServiceConnected(ComponentName name, IBinder service) {  
  8.         Log.i(TAG, "AIDLClient.onServiceConnected()...");  
  9.         iCountService = ICountService.Stub.asInterface(service);  
  10.     }  
  11. };  
          這裡的iCountService物件實際上就是ICountService的物件在此例項化。

        5.4.8 獲取當前下載進度並更新到介面上。

[java] view plaincopy
  1. Handler mHandler = new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         super.handleMessage(msg);  
  5.         try {  
  6.             int count =  iCountService.getCount();  
  7.             mTextView.setText(count+"%");  
  8.             mProgressBar.setProgress(count);  
  9.         } catch (RemoteException e) {  
  10.             e.printStackTrace();  
  11.         }  
  12.     }  
  13. };  
          通過更新介面上的進度條,可以狠容易的後去當前下載進度。因為AIDLServer端只是一個繼承自Service的服務,因此就不貼出其AndroidManifest.xml檔案了。

        5.5 小結

        AIDL在Android中是程式間通訊常用的方式,可能使用較為複雜,但效率高,擴充套件性好。同時很多系統服務就是以這種方式完成與應用程式通訊的。

        本文通過五個例子,分別介紹了五種與Service互動的方法,這些方法有的簡單,有的可能要複雜一些。在這裡只是做為對Servie的一些總結。後文附上原始碼下載連結,不需要積分的哦。:D

        原始碼下載

相關文章