之前所談的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);
}
}
相關連結:
我的Android開發相關文章