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); } }