Android官方文件—APP元件(Services)(Bound Services)
繫結服務
繫結服務是客戶端 - 伺服器介面中的伺服器。繫結服務允許元件(例如Activity)繫結到服務,傳送請求,接收響應,甚至執行程序間通訊(IPC)。繫結服務通常僅在其服務於另一個應用程式元件時才存在,並且不會無限期地在後臺執行。
本文件介紹如何建立繫結服務,包括如何從其他應用程式元件繫結到服務。但是,您還應該參閱“服務”文件以獲取有關服務的其他資訊,例如如何從服務傳遞通知,將服務設定為在前臺執行等等。
基礎
繫結服務是Service類的實現,它允許其他應用程式繫結到它並與之互動。要為服務提供繫結,必須實現onBind()回撥方法。此方法返回IBinder物件,該物件定義客戶端可用於與服務互動的程式設計介面。
客戶端可以通過呼叫bindService()繫結到服務。如果是這樣,它必須提供ServiceConnection的實現,它監視與服務的連線。 bindService()方法在沒有值的情況下立即返回,但是當Android系統在客戶端和服務之間建立連線時,它會呼叫ServiceConnection上的onServiceConnected(),以提供客戶端可用於與服務通訊的IBinder。
多個客戶端可以立即連線到該服務。但是,只有在第一個客戶端繫結時,系統才會呼叫服務的onBind()方法來檢索IBinder。然後,系統將相同的IBinder傳遞給任何繫結的其他客戶端,而無需再次呼叫onBind()。
當最後一個客戶端從服務解除繫結時,系統會銷燬該服務(除非該服務也是由startService()啟動的)。
實現繫結服務時,最重要的部分是定義onBind()回撥方法返回的介面。您可以通過幾種不同的方式定義服務的IBinder藉口,以下部分將討論每種技術。
繫結到已啟動的服務 如“服務”文件中所述,您可以建立既啟動又繫結的服務。也就是說,可以通過呼叫startService()來啟動服務,該服務允許服務無限期地執行,並且還允許客戶端通過呼叫bindService()來繫結到服務。 如果您確實允許啟動和繫結服務,那麼當服務啟動時,系統不會在所有客戶端解除繫結時銷燬服務。相反,您必須通過呼叫stopSelf()或stopService()來顯式停止服務。 雖然您通常應該實現onBind()或onStartCommand()其中一個,但有時需要同時實現它們。例如,音樂播放器可能會發現允許其服務無限期執行並提供繫結很有用。這樣,活動可以啟動服務以播放一些音樂,並且即使使用者離開應用程式,音樂也繼續播放。然後,當用戶返回到應用程式時,活動可以繫結到服務以重新獲得對回放的控制。 請務必閱讀有關管理繫結服務生命週期的部分,以獲取有關將繫結新增到已啟動服務時的服務生命週期的更多資訊。
建立繫結服務
建立提供繫結的服務時,必須提供IBinder,它提供客戶端可用於與服務互動的程式設計介面。您可以通過三種方式定義介面:
- 擴充套件Binder類
如果您的服務對您自己的應用程式是私有的並且在與客戶端相同的程序中執行(這是常見的),您應該通過擴充套件Binder類並從onBind()返回它的例項來建立您的介面。客戶端接收Binder並可以使用它直接訪問Binder實現甚至服務中可用的公共方法。 當您的服務僅僅是您自己的應用程式的後臺工作程式時,這是首選技術。您不以這種方式建立介面的唯一原因是因為您的服務被其他應用程式或跨單獨的程序使用。
- 使用Messenger
如果您需要在不同程序中使用介面,則可以使用Messenger為服務建立介面。通過這種方式,服務定義了一個響應不同型別的Message物件的Handler。此Handler是Messenger的基礎,然後可以與客戶端共享IBinder,允許客戶端使用Message物件向服務傳送命令。此外,客戶端可以定義自己的Messenger,以便服務可以發回訊息。 這是執行程序間通訊(IPC)的最簡單方法,因為Messenger將所有請求排隊到一個執行緒中,這樣您就不必將服務設計為執行緒安全的。
- 使用AIDL
AIDL(Android介面定義語言)執行所有工作,將物件分解為作業系統可以理解的原語,並跨程序對其進行編組以執行IPC。使用Messenger的先前技術實際上是基於AIDL作為其底層結構。如上所述,Messenger在單個執行緒中建立所有客戶端請求的佇列,因此服務一次接收一個請求。但是,如果您希望服務同時處理多個請求,則可以直接使用AIDL。在這種情況下,您的服務必須能夠進行多執行緒並且構建為執行緒安全的。
要直接使用AIDL,必須建立一個定義程式設計介面的.aidl檔案。 Android SDK工具使用此檔案生成實現介面並處理IPC的抽象類,然後您可以在服務中擴充套件該類。
注意:大多數應用程式不應使用AIDL來建立繫結服務,因為它可能需要多執行緒功能,並且可能導致更復雜的實現。因此,AIDL不適合大多數應用程式,本文件不討論如何將其用於您的服務。如果您確定需要直接使用AIDL,請參閱AIDL文件。
擴充套件Binder類
如果您的服務僅由本地應用程式使用,並且不需要跨程序工作,那麼您可以實現自己的Binder類,使您的客戶端可以直接訪問服務中的公共方法。
注意:僅當客戶端和服務位於同一應用程式和程序中時才有效,這是最常見的。例如,這適用於需要將活動繫結到其後臺播放音樂的服務的音樂應用程式。
以下是如何設定它:
1.在您的服務中,建立一個Binder例項:
- 包含客戶端可以呼叫的公共方法
- 返回當前的Service例項,該例項具有客戶端可以呼叫的公共方法
- 或者,使用客戶端可以呼叫的公共方法返回由服務託管的另一個類的例項
2.從onBind()回撥方法返回此Binder例項。
3.在客戶端中,從onServiceConnected()回撥方法接收Binder,並使用提供的方法呼叫繫結服務。
注意:服務和客戶端必須位於同一應用程式中的原因是客戶端可以強制轉換返回的物件並正確呼叫其API。服務和客戶端也必須處於同一個程序中,因為此技術不會跨程序執行任何編組。
例如,這是一個服務,通過Binder實現為客戶提供對服務中方法的訪問:
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(100);
}
}
LocalBinder為客戶端提供getService()方法,以檢索LocalService的當前例項。這允許客戶端呼叫服務中的公共方法。例如,客戶端可以從服務中呼叫getRandomNumber()。
這是一個繫結到LocalService的activity,並在單擊按鈕時呼叫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;
}
};
}
上面的示例顯示了客戶端如何使用ServiceConnection的實現和onServiceConnected()回撥繫結到服務。下一節提供有關繫結到服務的此過程的更多資訊。
注意:在上面的示例中,onStop()方法取消繫結客戶端與服務的繫結。客戶應在適當的時間解除服務繫結,如附加說明中所述。
有關更多示例程式碼,請參閱ApiDemos中的LocalService.java類和LocalServiceActivities.java類。
使用Messenger
與AIDL相比 當您需要執行IPC時,使用Messenger作為您的介面比使用AIDL實現它更簡單,因為Messenger將所有呼叫排隊到服務,而純AIDL介面向服務傳送同時請求,然後必須處理多執行緒。 對於大多數應用程式,該服務不需要執行多執行緒,因此使用Messenger允許服務一次處理一個呼叫。如果您的服務是多執行緒的,那麼您應該使用AIDL來定義您的介面。
如果您需要服務與遠端程序通訊,則可以使用Messenger為您的服務提供介面。此技術允許您執行程序間通訊(IPC),而無需使用AIDL。
以下是如何使用Messenger的摘要:
- 該服務實現一個Handler,它接收來自客戶端的每個呼叫的回撥。
- Handler用於建立Messenger物件(它是對Handler的引用)。
- Messenger建立一個IBinder,服務從onBind()返回到客戶端。
- 客戶端使用IBinder例項化Messenger(引用服務的Handler),客戶端使用它將Message物件傳送到服務。
- 該服務在其Handler中接收每個Message,具體來說,在handleMessage()方法中。
這樣,客戶端就沒有“方法”來呼叫服務。相反,客戶端提供服務在其處理程式中接收的“訊息”(訊息物件)。 這是一個使用Messenger介面的簡單示例服務:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* 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()方法是服務接收傳入訊息的位置,並根據成員決定要執行的操作。 客戶端需要做的就是根據服務返回的IBinder建立一個Messenger,並使用send()傳送訊息。例如,這是一個繫結到服務並將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, 0, 0);
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;
}
}
}
請注意,此示例未顯示服務如何響應客戶端。如果您希望服務響應,則還需要在客戶端中建立Messenger。然後,當客戶端收到onServiceConnected()回撥時,它會向send()方法的replyTo引數中包含客戶端Messenger的服務傳送一條訊息。
您可以在MessengerService.java(服務)和MessengerServiceActivities.java(客戶端)示例中看到如何提供雙向訊息傳遞的示例。
繫結到服務
應用程式元件(客戶端)可以通過呼叫bindService()繫結到服務。 Android系統然後呼叫服務的onBind()方法,該方法返回IBinder以與服務進行互動。
繫結是非同步的。 bindService()立即返回,不會將IBinder返回給客戶端。要接收IBinder,客戶端必須建立ServiceConnection例項並將其傳遞給bindService()。 ServiceConnection包含系統呼叫以傳遞IBinder的回撥方法。
注意:只有Activity,服務和內容提供者可以繫結到服務 - 您無法從廣播接收器繫結到服務。
因此,要從客戶端繫結到服務,您必須:
1.實現ServiceConnection。
您的實現必須覆蓋兩個回撥方法:
系統呼叫此方法來傳遞服務的onBind()方法返回的IBinder。
當與服務的連線意外丟失時,例如當服務崩潰或被殺死時,Android系統會呼叫此方法。客戶端解除繫結時不會呼叫此方法。
2.呼叫bindService(),傳遞ServiceConnection實現。
3.當系統呼叫onServiceConnected()回撥方法時,您可以使用介面定義的方法開始呼叫服務。
4.要斷開與服務的連線,請呼叫unbindService()。
如果當您的應用程式銷燬客戶端時,您的客戶端仍然繫結到服務,則銷燬會導致客戶端解除繫結。一旦完成與服務的互動,最好解除客戶端的繫結。這樣做可以使空閒服務關閉。有關繫結和取消繫結的適當時間的詳細資訊,請參閱其他說明。
例如,以下程式碼段通過擴充套件Binder類將客戶端連線到上面建立的服務,因此它必須做的就是將返回的IBinder強制轉換為LocalService類並請求LocalService例項:
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,它明確命名要繫結的服務(認為intent可能是隱式的)。
- 第二個引數是ServiceConnection物件。
- 第三個引數是一個標誌,指示繫結的選項。它應該通常是BIND_AUTO_CREATE,以便在服務尚未生效時建立服務。其他可能的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者0表示無。
補充
以下是有關繫結服務的一些重要說明:
- 您應始終捕獲DeadObjectException異常,這些異常在連線斷開時丟擲。這是遠端方法丟擲的唯一異常。
- 物件是跨程序的引用計數。
- 在匹配客戶生命週期的啟動和銷燬時,通常應該將繫結和解除繫結配對。例如:
1.如果您只需要在Activity可見時與服務進行互動,則應在onStart()期間繫結並在onStop()期間解除繫結。
2.如果您希望活動即使在後臺停止時也會收到響應,那麼您可以在onCreate()期間繫結並在onDestroy()期間解除繫結。請注意,這意味著您的活動需要在整個執行時使用該服務(即使在後臺執行),因此如果服務在另一個程序中,那麼您會增加程序的權重,系統將更有可能殺了它。
注意:在活動的onResume()和onPause()期間通常不應繫結和解除繫結,因為這些回撥在每個生命週期轉換時發生,您應該將這些轉換中發生的處理保持在最低限度。此外,如果應用程式中的多個活動繫結到同一服務,並且其中兩個活動之間存在轉換,則可能會銷燬該服務在當前活動解除繫結(暫停期間)之前(在恢復期間)之前重新建立該服務。 (活動文件中描述了活動如何協調其生命週期的活動轉換。)
有關更多示例程式碼,顯示如何繫結到服務,請參閱ApiDemos中的RemoteService.java類。
管理繫結服務的生命週期
當一個服務從所有客戶端解除繫結時,Android系統會將其銷燬(除非它也是以onStartCommand()啟動的)。因此,如果服務純粹是繫結服務,則不必管理服務的生命週期 - Android系統會根據是否繫結到任何客戶端來為您管理服務。
但是,如果您選擇實現onStartCommand()回撥方法,則必須顯式停止該服務,因為現在認為該服務已啟動。在這種情況下,服務一直執行,直到服務停止,stopSelf()或其他元件呼叫stopService(),無論它是否繫結到任何客戶端。
此外,如果您的服務已啟動並接受繫結,那麼當系統呼叫onUnbind()方法時,如果您希望在客戶端下次繫結到服務時接收對onRebind()的呼叫,則可以選擇返回true。 onRebind()返回void,但客戶端仍在其onServiceConnected()回撥中接收IBinder。下面,圖1說明了這種生命週期的邏輯。
有關已啟動服務的生命週期的詳細資訊,請參閱“服務”文件。