1. 程式人生 > >Android學習筆記(五三:服務Service(下- Remote Service

Android學習筆記(五三:服務Service(下- Remote Service

之前所談的Service屬於Local Service,即Service和Client在同一程序內(即同一application內),Service的生命週期服從程序的生命週期。在實際應用上,有時希望Service作為後臺服務,不僅被同一程序內的activity使用,也可被其他程序所使用,針對這種情況,需要採用bindService,也就是Remote Service的方式。

在Android中,不同app屬不同程序(process),程序是安全策略的邊界,一個程序不能訪問其他程序的儲存(例如採用ContentProvider)。在Remote Service中將涉及程序間通訊,也就是通常講的IPC(interprocess commnication),需要在程序A和程序B之間建立連線,以便進行相互的通訊或資料傳遞 。

Android提供AIDL(Android Interface Definition Language)工具幫助IPC之間介面的建立,大大地簡化了開發者檢視。右示意圖僅用於幫助理解程式碼。通過下面的步驟實現client和service之間的通訊:

【1】定義AIDL介面 ,Eclipse將自動為Service建立介面IService【2】Client連線Service,連線到IService暴露給Client的Stub,獲得stub物件;換句話,Service通過介面中的Stub向client提供服務,在IService中對抽象IService.Stub具體實現。 【3】Client和Service連線後,Client可向使用本地方法那樣,簡單地直接呼叫IService.Stub裡面的方法。

下面的例子給出client從提供定時計數的Remote Service,稱為TestRemoteService,中獲得服務的例子。

步驟1:通過AIDL檔案定義Service向client提供的介面,ITestRemoteService.aidl檔案如下

package com.wei.android.learning.part5; interface ITestRemoteService {     int getCounter(); }

我們在src的目錄下新增一個I<ServiceClassName>.aidl檔案,語法和java的相同。在這個例子中Service很簡單,只提供計數器的值,故在介面中我們定義了int getCounter( )。

AIDL檔案很簡單,Eclipse會根據檔案自動生成相關的一個java interface檔案,不過沒有顯示出來,如果直接使用命令列工具會幫助生成java檔案。

步驟2:Remote Service的編寫,通過onBind(),在client連線時,傳遞stub物件。 TestRemoteService.java檔案如下:

/* Service提供一個定時計數器,採用Runnable的方式實現,複習一下Android學習筆記(三一):執行緒:Message和Runnable中的例子3。為了避免干擾注意力,灰掉這部分程式碼。此外,我們提供showInfo(),用於跟蹤Service的執行情況,這部分也灰掉。*/public class TestRemoteService extends Service{     private Handler serviceHandler = null;    private int counter = 0;     private TestCounterTask myTask = new TestCounterTask();           public void onCreate() {          super.onCreate();         showInfo("remote service onCreate()");    }      public void onDestroy() {         super.onDestroy();         serviceHandler.removeCallbacks(myTask); //停止計數器        serviceHandler = null;         showInfo("remote service onDestroy()");    }      public void onStart(Intent intent, int startId) {        // 開啟計數器         super.onStart(intent, startId);         serviceHandler=new Handler();         serviceHandler.postDelayed(myTask, 1000);         showInfo("remote service onStart()");     }    //步驟2.1:具體實現介面中暴露給client的Stub,提供一個stub inner class來具體實現。     private ITestRemoteService.Stub stub= new ITestRemoteService.Stub() {       //步驟2.1:具體實現AIDL檔案中介面的定義的各個方法        public int getCounter() throws RemoteException {              showInfo("getCounter()");            return counter;         }     };          //步驟2.2:當client連線時,將觸發onBind(),Service向client返回一個stub物件,由此client可以通過stub物件來訪問Service,本例中通過stub.getCounter()就可以獲得計時器的當前計數。在這個例子中,我們向所有的client傳遞同一stub物件。    public IBinder onBind(Intent arg0) {          showInfo("onBind() " + stub); //我們特別跟蹤了stub物件的地址,可以在client連線service中看看通過ServiceConnection傳遞給client        return stub;    }      /* 用Runnable使用定時計數器,每10秒計數器加1。 */     private class TestCounterTask implements Runnable{        public void run() {              ++ counter;             serviceHandler.postDelayed(myTask,10000);             showInfo("running " + counter);         }     }      /* showInfo( ) 幫助我們進行資訊跟蹤,更好了解Service的執行情況 */    private void showInfo(String s){         System.out.println("[" +getClass().getSimpleName()+"@" + Thread.currentThread().getName()+ "] " + s);    } }

步驟3:Client和Service建立連線,獲得stub,ServiceTest4.java程式碼如下

public class ServiceTest4 extends Activity{    private ITestRemoteService remoteService = null; //步驟3.1 定義介面變數     private boolean isStarted = false;    private CounterServiceConnection conn = null;    //步驟3.1 定義連線變數,實現ServiceConnection介面           protected void onCreate(Bundle savedInstanceState) {          … … //5個button分別觸發startService( ),stopService( ) , bindService( ), releaseService( )和invokeService( ),下面兩行,一行是顯示從Service中獲得的計數值,一行顯示狀態。        }     private void startService(){         Intent i = new Intent();          i.setClassName("com.wei.android.learning", "com.wei.android.learning.part5.TestRemoteService"); //我的這個包裡面還有層次,如*.part1、*.part2,etc        startService(i); //和之前的local service一樣,通過intent開啟Service,觸發onCreate()[if Service沒有開啟]->onStart()        isStarted = true;          updateServiceStatus();      }     private void stopService(){         Intent i = new Intent();         i.setClassName("com.wei.android.learning","com.wei.android.learning.part5.TestRemoteService");        stopService(i); //觸發Service的 onDestroy()[if Service存在]        isStarted = false;         updateServiceStatus();     }       //步驟3.3:bindService( )通過一個實現ServiceConnection介面的類於Service之間建立連線,注意到裡面的引數Context.BIND_AUTO_CREATE,觸發onCreate()[if Service不存在] –> onBind()。    private void bindService(){          if(conn == null){             conn = new CounterServiceConnection();             Intent i = new Intent();             i.setClassName("com.wei.android.learning","com.wei.android.learning.part5.TestRemoteService");           bindService(i, conn,Context.BIND_AUTO_CREATE);            updateServiceStatus();         }     }     private void releaseService(){         if(conn !=null){             unbindService(conn); //斷開連線,解除繫結            conn = null;             updateServiceStatus();         }     }     private void invokeService(){          if(conn != null){             try{                 Integer counter = remoteService.getCounter(); //一旦client成功繫結到Service,就可以直接使用stub中的方法。                 TextView t = (TextView)findViewById(R.id.st4_notApplicable);                t.setText("Counter value : " + Integer.toString(counter));            }catch(RemoteException e){                 Log.e(getClass().getSimpleName(),e.toString());             }         }     }    //步驟3.2 class CounterServiceConnection實現ServiceConnection介面,需要具體實現裡面兩個觸發onServiceConnected()和onServiceDisconnected()     private class CounterServiceConnection implements ServiceConnection{        @Override         public void onServiceConnected(ComponentName name, IBinder service) {            // 從連線中獲得stub物件,根據我們的跟蹤,remoteService就是service中的stub物件             remoteService = ITestRemoteService.Stub.asInterface(service);             showInfo("onServiceConnected()" + remoteService);        }         @Override         public void onServiceDisconnected(ComponentName name) {             remoteService = null;             updateServiceStatus();             showInfo("onServiceDisconnected");         }     }     private void updateServiceStatus() {          TextView t = (TextView)findViewById( R.id.st4_serviceStatus);         t.setText( "Service status: "+(conn == null ? "unbound" : "bound")+ ","+ (isStarted ? "started" : "not started"; ));           }     private void showInfo(String s){          System.out.println("[" +getClass().getSimpleName()+"@" + Thread.currentThread().getName()+ "] " + s);    } }