原文位址:Bound Services
- 一、前言
- 二、基本内容
- 2.1 關于綁定已經start了的service的說明
- 三、建立bound service
- 3.1 繼承Binder類
- 3.2 使用Messenger
- 四、綁定service
- 4.1 其他說明
- 五、管理bound service的生命周期
一、前言
bound service是client-server接口中的server端。bound service允許其他元件綁定它,發送請求、擷取結果,允許程序間通信。bound service隻有在其他元件綁定它時才存活,不會像started service那樣無限的存活在背景。
本文檔描述了怎樣建立bound service,與怎樣通過其他應用元件綁定它。
二、基本内容
bound service是Service的一種實作,你需要實作Service類的onBind()回調方法。這個回調方法需要傳回一個IBinder對象,此對象定義了調用方與此bound service互動的接口。
調用方可以通過bindService()方法綁定一個bound service。這個方法的一個入參為ServiceConnection,你需要實作ServiceConnection接口以監控與service的連接配接情況。bindService()方法在調用後會立刻傳回,然後Android系統試圖在調用方與service間建立連接配接,當連接配接建立時,Android系統會調用ServiceConnection接口中的onServiceConnected()方法,以發送service的IBinder對象。
bound service可以同時被多個調用方綁定,但是系統隻會調用bound service的onBind()方法一次(在第一個調用方綁定它時)。然後其他調用方綁定時,系統會将同一個IBinder發送給它們。
當所有的調用方都與bound service解綁時,系統會銷毀此service(除非這個service還被startService()啟動了)。
在實作bound service時,最重要的就是定義onBind()方法傳回的通信接口IBinder。下面将介紹幾種定義IBinder的方法。
2.1 關于綁定已經start了的service的說明
你可以建立一個既可以被start又可以被bind的service,然後通過startService()啟動它(此service将在背景無限期運作),或者通過bindService()綁定它。
如果你的service同時支援兩種啟動方式,而且已經被start了,那麼當所有調用方都解綁時service不會被銷毀。你必須顯式的調用stopSelf()或stopService()來停止它。
三、建立bound service
在實作onBind()方法時,你需要傳回一個IBinder對象,你可以通過三種方式定義它:
-
繼承Binder類
如果你要建立的service是自己應用内的service,而且調用方在同一個程序運作,那麼你應該通過繼承Binder類來建立自己的binder,并通過onBind()傳回給調用方。調用方在獲得Binder執行個體後,可以通過它通路Binder或service實作的public方法。
如果你要建立的service僅僅用于本應用,那麼使用本種方式是較好的選擇。而如果你要建立的service是被其他應用使用的,或者通過其他的程序使用的,那麼請使用其他方式。
-
使用Messenger
如果你需要在其他程序使用binder,那麼你可以用Messenger為service建立它。在這種方式下,service會定義一個handler來響應不同的Message對象。Handler是Messenger的基礎,可以與調用方共同使用一個IBinder對象,允許調用方使用Message對象向service發送指令。另外,調用方也可以實作自己的Messenger,這樣的話,service就可以向它發送資訊了。
本種方式是實作程序間通信的最簡單的方法。Messenger會将所有請求以隊列的形式存在另外的線程中,是以你不需要将你的service設計為線程安全的。
-
使用AIDL
AIDL(Android interface definition language)可以将對象分解為作業系統可以了解的原始資料,這樣的話作業系統就可以将這些對象安排到不同的程序中,進而實作程序間通信。之前提到的Messenger,實際在内部結構上是基于AIDL的。Messenger會建立一個隊列存放調用方的請求,service每次可以收到一個請求。但是,如果你希望你的service可以同時處理多個請求,Messenger就不夠用了,你需要直接使用AIDL(在用AIDL方式時,你需要保證自己的service支援多線程且線程安全)。
使用AIDL方法,你需要聲明一個.aidl檔案,這個檔案中定義了你需要的接口。Android SDK工具會通過這個檔案生成一個實作了你需要的接口的抽象類來處理程序間通信,你可以在自己的service中繼承這個抽象類。
注意:大多數應用不需要使用AIDL建立bound service。而且使用AIDL需要更複雜的實作。是以,本文檔不對AIDL細節進行描述,如果想要更多資訊,請看單獨的一篇文檔:AIDL
3.1 繼承Binder類
通過此方式實作bound service需要以下步驟:
-
在service中,用以下方法之一建立Binder:
- 實作調用方需要的public方法
- 傳回目前Service對象,這個對象中有調用方需要的public方法
- 傳回某個類的執行個體,此類被service持有,且包含調用方需要的public方法
- 在onBind()方法中傳回此Binder
- 調用方通過onServiceConnected()方法擷取Binder對象,然後通過其中的方法與service通信
注意:service與調用方必須處于同一個應用,調用方才可以将onBind()傳回的對象強轉為需要的對象進而調用其API。service和調用也必須處于同一個程序,即此方式不支援跨程序通信。
下面是一個通過Binder實作bound service的示例:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt();
}
}
在上例中,LocalBinder提供getService()方法,調用方可以通過此方法擷取LocalService執行個體。然後,調用方就可以調用這個service的public方法了。例如,調用方可以調用service的getRandomNumber()方法。
下面是一個activity的示例代碼,其綁定了LocalService并在button被點選時調用getRandomNumber()方法:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
上面的示例顯示了調用方怎樣綁定service并通過onServiceConnected()擷取Binder。下個部分将會更詳細的介紹綁定service的過程。
注意:在上面的示例中沒有顯式的與service解綁,但是所有的調用方都應該在适當時候與service解綁,如activity進入pause狀态時
3.2 使用Messenger
下面是使用Messenger的步驟:
- service實作一個Handler以接收調用方的資訊
- 使用此Handler建立Messenger對象
- 通過Messenger建立IBinder并通過onBind()傳回給調用方
- 調用方通過IBinder建立Messenger,并通過此Messenger向service發送Message對象
- service在Handler中擷取Message
在上面的過程中,沒有說明service主動向調用方發送資訊的方法。隻是說明了調用方發送“message”而service擷取它的過程。
下面是使用Messenger的service的示例:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = ;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
在上面的這段代碼中,Handler中的handleMessage()方法會收到Message并根據Message中的what決定做什麼。
調用方需要做的是根據service傳回的IBinder建立Messenger,并調用其send()方法發送資訊。下面的例子說明了一個簡單的activity,其綁定了上面的service并發送了一個MSG_SAY_HELLO資訊:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, , );
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
上面的例子中沒有說明service如何響應調用方的message。你可以在調用方建立一個Messenger,當onServiceConnected()方法被調用時,通過send()方法的replyTo參數将其發送給service。進而讓service也可以向調用方發送資訊。
四、綁定service
應用元件(調用方)可以通過bindService()方法綁定service。然後Android系統會調用service的onBind()方法,并将service的IBinder傳回給調用方以供通信。
綁定過程是異步的。bindService()方法會立刻傳回,且并不會傳回IBinder對象。調用方需要在調用bindService()時傳入一個ServiceConnection對象,系統會通過此對象的回調方法分發IBinder。
注意:隻有activity、service、content provider可以綁定service,broadcast receiver不能綁定service
要綁定一個service,你需要:
-
實作ServiceConnection
你需要重寫兩個方法:
- onServiceConnected():系統通過此方法分發service通過onBind()方法傳回的IBinder
- onServiceDisconnected():當與service的連接配接意外斷開時系統會調用此方法,如service崩潰或者被kill時。調用方與service解綁,不會調用此方法
- 調用bindService()并傳入ServiceConnection
- 當系統調用onServiceConnected()時,擷取IBinder并與service通信
-
調用unbindService()與service解綁
如果調用方被銷毀,會自動與service解綁。但是你還是應該在通信結束或者activity進入pause狀态時主動與service解綁,以控制service的正常銷毀過程
下面是調用方綁定某個service并将傳回的IBinder強轉為LocalService并與service通信的示例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
上面代碼聲明的ServiceConnection需要通過下面代碼傳給bindService():
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()的三個參數說明如下:
- 第一個參數為Intent,其說明要綁定的service的名稱(本例中為顯式intent,你也可以使用隐式intent)
- 第二個參數為ServiceConnection
- 第三個參數為一個flag,其指明綁定的方式。通常此值為BIND_AUTO_CREATE,表示如果目标service執行個體不存在則建立一個service執行個體。其他可用的值有BIND_DEBUG_UNBIND、BIND_NOT_FOREGROUND、0
4.1 其他說明
下面是綁定service的一些重要說明:
- 你應該嘗試捕獲DeadObjectException異常,當連接配接中斷時會抛出此異常。此異常是remote方法會抛出的唯一異常
- 對象可以被跨程序引用
- 你應該根據調用方的生命周期成對的綁定與解綁service
注意:不要在activity的onResume()和onPause()方法綁定與解綁service,因為這兩個方法的過渡很頻繁,而且會導緻在多個activity綁定此service時出現問題
五、管理bound service的生命周期
當所有的調用方都解綁service時,系統會銷毀此service(除非此service還被onStartCommand()啟動了)。是以,如果你的service是一個單純的bound service,那麼你不需要管理它的生命周期。
然而,如果你實作了onStartCommand()方法,那麼你需要顯式的停止這個service,因為此service現在被認為是started service。這個service将會一直運作,直到調用stopSelf()或者stopService()方法,不管是不是所有的調用方都解綁了這個service。
如果你的service既被start又被bind,那麼當系統調用service的onUnbind()方法時,你可以選擇傳回true,這樣的話再有調用方綁定這個service時你會收到onRebind()回調。service生命周期詳見下圖。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX1MmeNlXQE90dJpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TMxEDN0IjMxEzNyQDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)