Android中程序與Service交互的方式——交互方式

分類(lèi): Android 962人閱讀 評(píng)論(1) 收藏 舉報(bào)
       上一篇文章:Android中程序與Service交互的方式——綜述 簡(jiǎn)述了Service的一些基礎(chǔ)知識(shí)以及Service和Thread的簡(jiǎn)單區(qū)別,本文將著重講解與Service交互的五種基本方式:廣播交互、共享文件交互、Mssenger(信使)交互、自定義接口交互、AIDL交互。

       1. 廣播交互

       提到Activity與Service的交互,可能狠多人首先想到的就是BroadCast——廣播。在Android中,廣播是系統(tǒng)提供的一種很好的交互方式。比如:在電池電量過(guò)低,開(kāi)機(jī)完成等情況下,系統(tǒng)都會(huì)發(fā)出相應(yīng)的系統(tǒng)廣播,我們的應(yīng)用程序只需要注冊(cè)相應(yīng)的廣播接收器,就可以接收到這些系統(tǒng)的廣播。同時(shí),我們也可以定義自己的廣播,這樣在不同的Activity、Service以及應(yīng)用程序之間,就可以通過(guò)廣播來(lái)實(shí)現(xiàn)交互。我們通過(guò)模擬應(yīng)用程序后臺(tái)下載的情況來(lái)分析Service與Activity的交互方式。實(shí)現(xiàn)效果如圖1.1:

圖1.1

        當(dāng)我們點(diǎn)擊StartService按鈕之后,界面上的進(jìn)度條將會(huì)每隔一秒加1。因?yàn)槭悄M下載,因此下載動(dòng)作我們?cè)赟ervice中通過(guò)一個(gè)Timer定時(shí)器來(lái)實(shí)現(xiàn),在Timer中對(duì)一個(gè)整型數(shù)據(jù)i進(jìn)行自加(i++),然后Client端獲取Server端的i值并顯示在界面上,從而達(dá)到模擬的目的。

        1.1. 實(shí)現(xiàn)原理

        Server端將目前的下載進(jìn)度,通過(guò)廣播的方式發(fā)送出來(lái),Client端注冊(cè)此廣播的監(jiān)聽(tīng)器,當(dāng)獲取到該廣播后,將廣播中當(dāng)前的下載進(jìn)度解析出來(lái)并更新到界面上。

        1.2. 實(shí)現(xiàn)步驟

        1.2.1 在Client端中通過(guò)startService()啟動(dòng)Service。

  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()方法可以獲取當(dāng)前進(jìn)程的ID號(hào)。
       1.2.2 DownLoadService接到啟動(dòng)的命令之后,執(zhí)行onCreate()方法,并在其中開(kāi)啟timer計(jì)數(shù)模擬下載。

 

  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端傳送數(shù)據(jù)用的,使用的action是”com.seven.broadcast”,Client端只有註冊(cè)了相應(yīng)action才能夠接收到Server端的廣播,并解析其中的內(nèi)容。Process.myPid()是獲取當(dāng)前進(jìn)程的ID。
        1.2.3 在Server端的timer計(jì)數(shù)其中發(fā)送廣播,告知Client端目前下載進(jìn)度。

 

  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. }  
          通過(guò)intent.putExtra(key,value);設(shè)置intent的值,然后通過(guò)sendBroadcast(intent)2方法,將廣播發(fā)送出去。

 

       1.2.4 在Client端通過(guò)匿名內(nèi)部類(lèi)的方式實(shí)例化BroadcastReceiver并覆寫(xiě)其中的onReceive()方法。

  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端發(fā)送的廣播,如果是則對(duì)廣播中攜帶的intent數(shù)據(jù)進(jìn)行解包處理。這裡也可以單獨(dú)寫(xiě)一個(gè)類(lèi)繼承自BroadcastReceiver,在其中覆寫(xiě)onReceive()方法,在Client端中實(shí)例化其對(duì)象,同樣可以達(dá)到相應(yīng)的效果,這樣做可以為后面實(shí)現(xiàn)靜態(tài)注冊(cè)廣播。
        1.2.5 更新主介面下載進(jìn)度。

 

  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. };  
             這里對(duì)獲取到的進(jìn)度進(jìn)行了一次判斷,如果獲取到的值沒(méi)有異常,那么將會(huì)顯示到界面,并更新進(jìn)度條的進(jìn)度,如果異常則返回。
         1.2.6 一定要對(duì)Broadcast進(jìn)行注冊(cè)和取消注冊(cè)。只有注冊(cè)之后相應(yīng)的broadcast之后才能接收到廣播注冊(cè)方法有兩種。
         動(dòng)態(tài)注冊(cè)/取消注冊(cè):

 

  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. }  
           動(dòng)態(tài)註冊(cè)可以隨時(shí)註冊(cè)隨時(shí)取消。

 

        靜態(tài)註冊(cè):

  1. <receiver android:name="MyBroadcastReceiver">  
  2.     <intent-filter>  
  3.         <action android:name="com.seven.broadcast" />  
  4.     </intent-filter>  
  5. </receiver>  
           注:這里的MyBroadcastReceiver是一個(gè)繼承自BroadcastReceiver的類(lèi)。靜態(tài)注冊(cè)只要注冊(cè)了一次那么只要該程序沒(méi)有被卸載那么該廣播將一直有效。

 

        最后貼出整個(gè)AndroidManifest.xml文件

  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運(yùn)行在單獨(dú)進(jìn)程中,從而可以模擬跨進(jìn)程通信。

 

       1.3 小結(jié)

       通過(guò)廣播的方式實(shí)現(xiàn)Activity與Service的交互操作簡(jiǎn)單且容易實(shí)現(xiàn),可以勝任簡(jiǎn)單級(jí)的應(yīng)用。但缺點(diǎn)也十分明顯,發(fā)送廣播受到系統(tǒng)制約。系統(tǒng)會(huì)優(yōu)先發(fā)送系統(tǒng)級(jí)廣播,在某些特定的情況下,我們自定義的廣播可能會(huì)延遲。同時(shí)在廣播接收器中不能處理長(zhǎng)耗時(shí)操作,否則系統(tǒng)會(huì)出現(xiàn)ANR即應(yīng)用程序無(wú)響應(yīng)。

        2. 共享文件交互

        這里提到的共享文件指的是Activity和Service使用同一個(gè)文件來(lái)達(dá)到傳遞數(shù)據(jù)的目的。我們使用SharedPreferences來(lái)實(shí)現(xiàn)共享,當(dāng)然也可以使用其它IO方法實(shí)現(xiàn),通過(guò)這種方式實(shí)現(xiàn)交互時(shí)需要注意,對(duì)于文件的讀寫(xiě)的時(shí)候,同一時(shí)間只能一方讀一方寫(xiě),不能兩方同時(shí)寫(xiě)。實(shí)現(xiàn)效果如圖2.1:

圖2.1

         2.1 實(shí)現(xiàn)原理

         Server端將當(dāng)前下載進(jìn)度寫(xiě)入共享文件中,Client端通過(guò)讀取共享文件中的下載進(jìn)度,并更新到主界面上。

         2.2 實(shí)現(xiàn)步驟

         2.2.1 在Client端通過(guò)startService()啟動(dòng)Service。

  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只是為了啟動(dòng)Server端。
        2.2.2 Server端收到啟動(dòng)intent之后執(zhí)行onCreate()方法,并開(kāi)啟timer,模擬下載,以及初始化SharedPreferences對(duì)象preferences。

 

  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. }  
          通過(guò)preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs文件夾下建立相應(yīng)的xml文件。

 

       2.2.3 開(kāi)始計(jì)數(shù)并將下載進(jìn)度寫(xiě)入shared_prefs文件夾下的xml文件中,內(nèi)容以鍵值對(duì)的方式保存。

  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. }  
           對(duì)於SharedPreferences的使用需要注意一下幾點(diǎn):

 

        首先,使用sharedPreferences前需要獲取文件引用。

        preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);

        其次,使用sharedpreferences寫(xiě)數(shù)據(jù)方式。

        preferences.edit().putInt("CurrentLoading", i).commit();

        最后,讀取數(shù)據(jù)的方式。

        int couLoad = preferences.getInt("CurrentLoading", 0);

        2.2.4 Client端通過(guò)讀取/data/data/com.seven.servicetestdemo/shared_prefs文件夾下的xml文件,并取得里面的鍵值對(duì),從而獲取到當(dāng)前的下載進(jìn)度,并更新到主界面上。

  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 小結(jié)

 

        因?yàn)榉椒ê?jiǎn)單,因此就不貼出AndroidManifest.xml文件了。對(duì)於這種方式實(shí)現(xiàn)Activity與Service的交互,可以說(shuō)很方便,就像使用管道,一個(gè)往裡寫(xiě),一個(gè)往外讀。但這種方式也有缺陷,寫(xiě)入數(shù)據(jù)較為復(fù)雜以及數(shù)據(jù)量較大時(shí),就有可能導(dǎo)致寫(xiě)入與讀數(shù)據(jù)出不一致的錯(cuò)誤。同時(shí)因?yàn)榻?jīng)過(guò)了一個(gè)中轉(zhuǎn)站,這種操作將更耗時(shí)。

        3. Messenger交互(信使交互)

        Messenger翻譯過(guò)來(lái)指的是信使,它引用了一個(gè)Handler對(duì)象,別人能夠向它發(fā)送消息(使用mMessenger.send(Message msg)方法)。該類(lèi)允許跨進(jìn)程間基于Message通信,在服務(wù)端使用Handler創(chuàng)建一個(gè) Messenger,客戶端只要獲得這個(gè)服務(wù)端的Messenger對(duì)象就可以與服務(wù)端通信了。也就是說(shuō)我們可以把Messenger當(dāng)做Client端與Server端的傳話筒,這樣就可以溝通交流了。實(shí)現(xiàn)效果如圖3.1:

圖3.1

        3.1 實(shí)現(xiàn)原理

        在Server端與Client端之間通過(guò)一個(gè)Messenger對(duì)象來(lái)傳遞消息,該對(duì)象類(lèi)似于信息中轉(zhuǎn)站,所有信息通過(guò)該對(duì)象攜帶。

        3.2 Messenger的一般用法

        (1). 在Server端創(chuàng)建信使對(duì)象。

               mMessenger = new Messenger(mHandler)

        (2). Client端使用bindService()綁定Server端。

        (3). Server端的onBind()方法返回一個(gè)binder對(duì)象。

               return mMessenger.getBinder();

        (4). Client端使用返回的binder對(duì)象得到Server端信使。

  1. public void onServiceConnected(ComponentName name, IBinder service) {    
  2.               rMessenger = new Messenger(service);        
  3.              ......  
  4.  }  
          這里雖然是new了一個(gè)Messenger,但我們查看它的實(shí)現(xiàn)

 

  1. public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);  }   
           發(fā)現(xiàn)它的mTarget是通過(guò)AIDL得到的,實(shí)際上就是遠(yuǎn)程創(chuàng)建的那個(gè)。

 

        (5). Client端可以使用這個(gè)Server端的信使對(duì)象向Server端發(fā)送消息。
               rMessenger.send(msg);

        這樣Server端的Handler對(duì)象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中進(jìn)行處理。經(jīng)過(guò)這5個(gè)步驟之后只有Client端向Server端發(fā)送消息,這樣的消息傳遞是單向的,那么如何實(shí)現(xiàn)消息的雙向傳遞呢?

        首先需要在第5步做修改,在send(msg)前通過(guò)msm.replyTo = mMessenger將Client端自己的信使設(shè)置到消息中,這樣Server端接收到消息時(shí)同時(shí)也得到了Client端的信使對(duì)象,然后Server端也可以通過(guò)使用得到的Client端的信使對(duì)象來(lái)項(xiàng)Client端發(fā)送消息 cMessenger = msg.replyTo2  cMessenger.send(message);

       這樣即完成了從Server端向Client端發(fā)送消息的功能,這樣Client端可以在自己的Handler對(duì)象的handlerMessage()方法中接收服務(wù)端發(fā)送來(lái)的message進(jìn)行處理。

        3.3 實(shí)現(xiàn)步驟

        3.3.1 創(chuàng)建并初始化Server端的信使對(duì)象。

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

 

  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()方法中返回一個(gè)binder對(duì)象。

 

  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "MessengerService.onBind()...");  
  4.     return mMessenger.getBinder();  
  5. }  
          這裡的mMessenger就是Server端的信使對(duì)象。

 

        3.3.4 Client端使用ServiceConnected()方法來(lái)獲取Server端的信使對(duì)象。

  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端的信使對(duì)象的同時(shí),也初始化Client端的自己的信使對(duì)象,并且通過(guò)sendMessage()方法發(fā)送消息給Server端,表示可以開(kāi)始下載了。

 

        3.3.5 Client端使用獲取到的rMessenger來(lái)發(fā)送消息給Server端,同時(shí)將Client端的信使封裝到消息中,一并發(fā)送給Server端。

  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端里的一個(gè)靜態(tài)常量。Msg.replyTo=mMessenger;表示發(fā)送給Server端的信息里攜帶Client端的信使。

 

        3.3.6 Server端獲取Client端發(fā)送的消息并得到Client端的信使對(duì)象。

  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端開(kāi)啟timer模擬下載,并接收Client端的信使對(duì)象。

 

        3.3.7 Server端向Client端發(fā)送數(shù)據(jù)。

  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端的信使對(duì)象來(lái)發(fā)送當(dāng)前下載進(jìn)度給Client端。

 

        3.3.8 Client端接收來(lái)自Server端的數(shù)據(jù)。


  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端的接收狠類(lèi)似。接收到Server端傳過(guò)來(lái)的數(shù)據(jù)之后進(jìn)行介面更新,以及下載進(jìn)度更新。

 

        以下是AndroidManifest.xml文件:

  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的註冊(cè)中加入了過(guò)濾動(dòng)作,只有相匹配的action才能啟動(dòng)相應(yīng)的Service。

 

        3.4 小結(jié)

        通過(guò)Messenger來(lái)實(shí)現(xiàn)Activity和Service的交互,稍微深入一點(diǎn)我們就可以知道,其實(shí)Messenger也是通過(guò)AIDL來(lái)實(shí)現(xiàn)的。對(duì)於前兩種實(shí)現(xiàn)方式,Messenger方式總體上來(lái)講也是比較容易理解的,這就和平時(shí)使用Handler和Thread通信一個(gè)道理。

        4. 自定義接口交互

        何謂自定義接口呢,其實(shí)就是我們自己通過(guò)接口的實(shí)現(xiàn)來(lái)達(dá)到Activity與Service交互的目的,我們通過(guò)在Activity和Service之間架設(shè)一座橋樑,從而達(dá)到數(shù)據(jù)交互的目的,而這種實(shí)現(xiàn)方式和AIDL非常類(lèi)似(后文會(huì)說(shuō)到)。實(shí)現(xiàn)效果如圖4.1:

圖4.1

        4.1 實(shí)現(xiàn)原理

        自定義一個(gè)接口,該接口中有一個(gè)獲取當(dāng)前下載進(jìn)度的空方法。Server端用一個(gè)類(lèi)繼承自Binder并實(shí)現(xiàn)該接口,覆寫(xiě)了其中獲取當(dāng)前下載進(jìn)度的方法。Client端通過(guò)ServiceConnection獲取到該類(lèi)的對(duì)象,從而能夠使用該獲取當(dāng)前下載進(jìn)度的方法,最終實(shí)現(xiàn)實(shí)時(shí)交互。

        4.2 實(shí)現(xiàn)步驟

        4.2.1 新建一個(gè)Interface,并在其中創(chuàng)建一個(gè)用于獲取當(dāng)前下載進(jìn)度的的空方法getCurrentLoad()。

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

        4.2.2 新建Server端DownService實(shí)現(xiàn)ICountService并在其中通過(guò)一個(gè)內(nèi)部類(lèi)ServiceBinder繼承自Binder并實(shí)現(xiàn)ICoutService接口。

 

  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端中,實(shí)現(xiàn)獲取下載進(jìn)度的空方法getCurrentLoad();這是Eclipse自動(dòng)生成的,重點(diǎn)不在這裡。我們需要在ServiceBinder類(lèi)中覆寫(xiě)getCurrentLoad()方法,這裡我們返回當(dāng)前的下載進(jìn)度i。

 

       4.2.3 Client端使用bindService()綁定Server端。

  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);//這里一定要延遲一下再開(kāi)始獲取數(shù)據(jù),不然會(huì)報(bào)空指針異常   
  5. }  
           在Client端綁定Server端的同時(shí),延遲1s開(kāi)始獲取下載進(jìn)度。其中的intent = new Intent(“com.seven.test”)2com.seven.test該字符串要與在AndroidManifest.xml中申明的一致。

 

       4.2.4 Server端返回binder對(duì)象。

  1. @Override  
  2. public IBinder onBind(Intent intent) {  
  3.     Log.i(TAG, "DownLoadService.onBind()...");  
  4.     return serviceBinder;  
  5. }  
           這里的serviceBinder因?yàn)槔^承了Binder因此也是Binder對(duì)象。

 

        4.2.5 Client端通過(guò)ServiceConnection來(lái)獲取Server端的binder對(duì)象。

  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. };  
          獲取的過(guò)程是在bindService()過(guò)程中完成的,這里的iCountService是接口ICountService的對(duì)象,在這里得到實(shí)例化。

 

        4.2.6 在綁定完成之后,Server端會(huì)開(kāi)啟下載,在實(shí)際情況中Server端會(huì)開(kāi)啟獨(dú)立線程用于下載,這里用i++來(lái)代替。

  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()方法執(zhí)行之后會(huì)調(diào)用DownLoadService中的onCreate()方法,在其onCreate()方法中開(kāi)啟timer使得i++。

 

        4.2.7 Server端已經(jīng)開(kāi)啟了下載,那么Client端需要及時(shí)獲取下載進(jìn)度并在主界面上更新。

  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秒再開(kāi)始獲取下載進(jìn)度,獲取方法是直接通過(guò)int curLoad = iCountService.getCurrentLoad();這里的getCurrentLoad()方法是DownLoadService內(nèi)部類(lèi)ServiceBinder中的方法。Client端將獲取到的下載進(jìn)度更新到介面上并更新進(jìn)度條。

 

        4.3 小結(jié)

        通過(guò)上面的例子可以知道,這種方法簡(jiǎn)單實(shí)用,擴(kuò)展性強(qiáng),但其也有一些缺點(diǎn),比如需要延遲一些再開(kāi)始獲取Server端的數(shù)據(jù),從而無(wú)法完全實(shí)現(xiàn)從零開(kāi)始同步更新。綜其所述,通過(guò)自定義接口實(shí)現(xiàn)Activity與Service交互的方法還是比較實(shí)用的。適用於同進(jìn)程中通信,不能進(jìn)行跨進(jìn)程通信。

        5. AIDL交互

        什么是AIDL?

        AIDL是Android Interface Definition Language的首字母縮寫(xiě), 也就是Android接口定義語(yǔ)言。提及AIDL就不得不說(shuō)下Android的服務(wù),Android 支持兩種服務(wù)類(lèi)型的服務(wù)即本地服務(wù)和遠(yuǎn)程服務(wù)。

        本地服務(wù)無(wú)法供在設(shè)備上運(yùn)行的其他應(yīng)用程序訪問(wèn),也就是說(shuō)只能該應(yīng)用程序內(nèi)部調(diào)用,比如某些應(yīng)用程序中的下載類(lèi)服務(wù),這些服務(wù)只能由內(nèi)部調(diào)用。而對(duì)于遠(yuǎn)程服務(wù),除了可以由本應(yīng)用程序調(diào)用,還可以允許其他應(yīng)用程序訪問(wèn)。遠(yuǎn)程服務(wù)一般通過(guò)AIDL來(lái)實(shí)現(xiàn),可以進(jìn)行進(jìn)程間通信,這種服務(wù)也就是遠(yuǎn)程服務(wù)。

        本地服務(wù)與遠(yuǎn)程服務(wù)還是有一些重要的區(qū)別。具體來(lái)講,如果服務(wù)完全只供同一進(jìn)程中的組件使用(運(yùn)行后臺(tái)任務(wù)),客戶端一邊通過(guò)調(diào)用 Context.startService()來(lái)啟動(dòng)該服務(wù)。這種類(lèi)型的服務(wù)為本地服務(wù),它的一般用途是后臺(tái)執(zhí)行長(zhǎng)耗時(shí)操作。而遠(yuǎn)程服務(wù)一般通過(guò)bindService()方法啟動(dòng),主要為不同進(jìn)程間通信。我們也將遠(yuǎn)程服務(wù)稱為AIDL支持服務(wù),因?yàn)榭蛻舳耸褂?AIDL 與服務(wù)通信。Android中對(duì)于遠(yuǎn)程服務(wù)有多種叫法:遠(yuǎn)程服務(wù)、AIDL服務(wù)、外部服務(wù)和RPC服務(wù)。

        5.1 AIDL實(shí)現(xiàn)流程圖

圖5.1

        這屬于代理/存根結(jié)構(gòu),通過(guò)這張AIDL的流程圖,很容易發(fā)現(xiàn)Android實(shí)現(xiàn)IPC其實(shí)是在原來(lái)的C/S框架上加入了代理/存根結(jié)構(gòu)。

        比如,你到自動(dòng)取款機(jī)上去取款。那么你就是客戶(Client),取款機(jī)就是你的代理(Proxy);你不會(huì)在乎錢(qián)具體放在那里,你只想將你的錢(qián)從取款機(jī)中取出來(lái)。你同銀行之間的操作完全是取款機(jī)代理實(shí)現(xiàn)。你的取款請(qǐng)求通過(guò)取款機(jī)傳到另一邊,即銀行的服務(wù)器(Server)。它也沒(méi)有必要知道你在哪兒取錢(qián),它所關(guān)心的是你的身份和你取款多少。當(dāng)它確認(rèn)你的權(quán)限,就進(jìn)行相應(yīng)的操作,返回操作結(jié)果給取款機(jī),取款機(jī)根據(jù)服務(wù)器返回結(jié)果,從保險(xiǎn)柜里取出相應(yīng)數(shù)量的錢(qián)給你。你取出卡后,操作完成。取款機(jī)不是直接同服務(wù)器連接的,他們之間還有一個(gè)“存根(Stub)”,取款機(jī)與存根通信,服務(wù)器與存根通信,從某種意義上說(shuō)存根就是服務(wù)器的代理。實(shí)現(xiàn)效果如圖5.2:

圖5.2

        5.3 實(shí)現(xiàn)原理

        AIDL屬于Android的IPC機(jī)制,常用于跨進(jìn)程通信,主要實(shí)現(xiàn)原理基于底層Binder機(jī)制。

        5.4 實(shí)現(xiàn)步驟

        5.4.1 建立工程。按照?qǐng)D5.3和圖5.4建立AIDLServer端以及AIDLClient端。在AIDLServer端中只有一個(gè)服務(wù)程序,沒(méi)有主界面,其主要功能就是負(fù)責(zé)下載。AIDLClient端從AIDLServer端獲取當(dāng)前下載進(jìn)度(注:AIDLServer端和AIDLClient端是不同的兩個(gè)APK,在模擬本例的時(shí)候,需要先在模擬器上安裝AIDLServer編譯出來(lái)的APK,安裝方法可以直接在模擬器上運(yùn)行一次,可以通過(guò)adb install your.apk 來(lái)安裝)。

圖5.3

        AIDLServer端中新建了一個(gè)ICountService.aidl的文件,該文件內(nèi)容如下:

  1. package com.seven.aidlserver;  
  2.   
  3. interface ICountService{  
  4.     int getCount();  
  5. }  

          aidl文件的書(shū)寫(xiě)規(guī)范如下:

 

        (1). Android支持String和CharSequence(以及Java的基本數(shù)據(jù)類(lèi)型);

        (2). 如果需要在aidl中使用其它aidl接口類(lèi)型,需要import,即使是在相同包結(jié)構(gòu)下;

        (3). Android允許傳遞實(shí)現(xiàn)Parcelable接口的類(lèi),需要import;

        (4). Android支持集合接口類(lèi)型List和Map,但是有一些限制,元素必須是基本型或者前面三種情況,不需要import集合接口類(lèi),但是需要對(duì)元素涉及到的類(lèi)型import;

        (5). 非基本數(shù)據(jù)類(lèi)型,也不是String和CharSequence類(lèi)型的,需要有方向指示,包括in、out和inout,in表示由客戶端設(shè)置,out表示由服務(wù)端設(shè)置,inout是兩者均可設(shè)置。

圖5.4

         AIDLClient端需要將AIDLServer端的ICountService.aidl文件復(fù)製過(guò)去,這裡為了方便,新建了一個(gè)和Server端同名的包,并將ICountService.aidl放與其中。

         5.4.2 我們?cè)赟erver端建立好ICoutService.aidl文件之后,Eclipse會(huì)在/gen/com.seven.aidlserver/目錄下自動(dòng)生成ICountService.java文件。該文件由Eclipse自動(dòng)生成,請(qǐng)勿隨便修改,后文我們需引用到的內(nèi)容如下:

  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端新建一個(gè)內(nèi)部類(lèi)繼承自ICountService.Stub并覆寫(xiě)其中的getCount()方法,以及實(shí)例化該類(lèi)的一個(gè)對(duì)象serviceBinder。

 

  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. }  
         這里與前面提到的“通過(guò)接口實(shí)現(xiàn)交互”非常類(lèi)似。

 

        5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder對(duì)象。

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

         5.4.5 在Server端的onCreate()方法中,開(kāi)啟timer,模擬下載。在Client端通過(guò)bindService()綁定Server端的時(shí)候,會(huì)首先執(zhí)行Server端的onCreate()方法。

 


  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端通過(guò)bindService()綁定Server端。

 


  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端注冊(cè)Service時(shí)過(guò)濾的要一致,也就是說(shuō)只有發(fā)出相同的action才會(huì)啟動(dòng)該Service。同時(shí)開(kāi)啟了一個(gè)timer用于獲取下載進(jìn)度。
        5.4.7 Client端通過(guò)ServiceConnection來(lái)獲取Server端的binder對(duì)象。

 

  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對(duì)象實(shí)際上就是ICountService的對(duì)象在此實(shí)例化。

 

        5.4.8 獲取當(dāng)前下載進(jìn)度并更新到界面上。

  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. };  
          通過(guò)更新介面上的進(jìn)度條,可以狠容易的后去當(dāng)前下載進(jìn)度。因?yàn)锳IDLServer端只是一個(gè)繼承自Service的服務(wù),因此就不貼出其AndroidManifest.xml文件了。

 

        5.5 小結(jié)

        AIDL在Android中是進(jìn)程間通信常用的方式,可能使用較為復(fù)雜,但效率高,擴(kuò)展性好。同時(shí)很多系統(tǒng)服務(wù)就是以這種方式完成與應(yīng)用程序通信的。

        本文通過(guò)五個(gè)例子,分別介紹了五種與Service交互的方法,這些方法有的簡(jiǎn)單,有的可能要復(fù)雜一些。在這里只是做為對(duì)Servie的一些總結(jié)。后文附上源碼下載鏈接,不需要積分的哦。:D

        源碼下載

        所有源碼均在Ubuntu 10.04 Eclipse-Indigo下實(shí)驗(yàn)通過(guò) 模擬器采用的是2.3的鏡像

查看評(píng)論
1樓 lpw14 2012-07-04 17:10發(fā)表 [回復(fù)] [引用] [舉報(bào)]
總結(jié)的很好,不過(guò)還有一種交互方式?jīng)]提到。
  1. boolean android.os.Binder.transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException  

server端:
  1. <BR>public class ServiceBinder extends Binder {<BR>     @Override<BR>       protected boolean onTransact(int code, Parcel data, Parcel reply,<BR>               int flags) throws RemoteException {<BR>         reply.writeInt(i);<BR>          return super.onTransact(code, data, reply, flags);<BR>      }<BR>   }<BR>  


client端:
  1. Parcel data = Parcel.obtain();  
  2. Parcel reply = Parcel.obtain();  
  3. try {  
  4.     mBinder.transact(0, data, reply, 0);  
  5. catch (RemoteException e) {  
  6.         e.printStackTrace();  
  7. }