Android四大元件之Service第二章:Bound Service
這一章介紹bound service(我也不知道怎麼翻譯比較好。。)。建議先看前一章對服務有個大概的瞭解了再看bound service。
Android四大元件之Service第一章:service及其生命週期
bound service是一種採用了client-server架構(CS架構)的介面,它使得元件同一個服務進行繫結後的傳送請求,接收結果,IPC通訊成為可能。本文將介紹如何建立一個bound service並與之繫結。
基礎知識
在service這邊,為了實現繫結,必須重寫onBind()方法,該方法返回一個IBinder物件,這個物件決定了client與server之間的通訊方式。
在client這邊,必須要呼叫bindService()與服務繫結,同時要提供一個ServiceConnection物件,這個物件會監控client與server之間的繫結情況。當二者的繫結關係建立的時候,就會呼叫ServiceConnection的onServiceConnected()方法並傳入一個IBinder物件,這個物件就是service那邊的onBind()方法執行完之後返回的IBinder物件。可以說ServiceConnection是二者之前建立關係的橋樑。
一個service可以同時繫結多個client,但是其onBind()方法只會在首次繫結的時候呼叫,之後繫結更多client的時候不會再呼叫這個方法,但是之後繫結的client依然會收到同樣的IBinder物件。(作者注:可見onBind()方法返回的IBinder物件並不是直接返回給client,而是由系統接管,再派發給client。)
當最後一個client解綁的時候,系統將會銷燬這個服務,除非這個服務還是一個started service。
對於一個bound service來講,最重要的是如何與client通訊,即返回一個什麼樣的IBinder。下面介紹三種不同的IBinder。
建立一個bound service
建立一個bound service的核心在於要能夠提供一個client能夠和該service進行互動的IBinder物件(準確來說是介面,因為IBinder是介面,不能生成物件)。有三種方式來提供這種介面:
1. 繼承Binder類
如果一個服務只為自身所在的應用程式服務,並且在同一個程序中跑,那麼繼承Binder類,並在onBind()方法中返回該類物件的方式是最合適的。client將會使用這個Binder物件來呼叫Binder或Service中的public方法。
如果一個服務可能被其他程式所用或者需要執行在不同的程序中,那麼就無法使用該方式,否則推薦使用該方式來建立一個bound service。
2. 使用Messenger
如果你需要跨程序通訊,那可以使用Messenger通訊的方式來提供這個操作介面。在這種方式下,Service內部有一個Handler物件用來處理各種不同的Message物件,根據這個Handler物件可以建立一個Messenger。這裡很難說清楚,具體看下面的程式碼示例吧。
如果要進行IPC的話,使用Messenger是最簡單的方式了,因為Messenger會將所有的請求列隊,依次處理。因此不需要擔心執行緒安全問題。
3. 使用AIDL
上面的Messenger方式實際上是基於AIDL。正如上面提到的那樣,Messenger會將所有的請求列隊,依次處理。如果你一定要你的service能夠同時處理多個request,那就直接用AIDL吧。注意執行緒安全問題哦。
大多數應用程式不應該直接使用AIDL來建立bound service,因為這會讓邏輯變得很複雜。本章節不討論直接使用AIDL的方式。
繼承Binder類
如上所述,如果一個服務是一個本地服務,並且不需要跨程序,那麼可以使用這種方式向client提供一個Binder物件使得其能夠直接操作service中的public方法。
使用步驟
- 在service的內部生成一個Binder物件(成員變數),該Binder物件需要符合以下幾個特徵之一:
- 該Binder物件本身包含一些public方法
- 該Binder物件能夠返回其所在的Service物件,並且該Service物件中要包含public方法
- 該Binder物件能夠返回Service中的另外一些包含public方法的物件
- 在onBind()方法中返回該Binder物件
- 在client中,從onServiceConnected()方法中接收到這個Binder物件,進而通過其呼叫一些方法。
service和client必須在同一個應用程式中才行,這樣client才能對收到的Binder物件進行正確的cast並呼叫一些api。同時service和client也必須在同一個程序中,因為該技術並不支援程序間的通訊。
總結一下繼承Binder類的方式就是:向client提供引用,然後client使用該引用直接呼叫物件的public方法。
以下是程式碼示例:
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);
}
}
上面的程式碼中有個關鍵方法:getService()。該方法返回了一個Service物件的引用,然後client直接使用這個引用來操作物件的public方法。下面來看看Activity(client)中的程式碼:
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;
}
};
}
可以看到在client的onStart()方法中進行了對服務的繫結操作,傳入了一個ServiceConnection物件。在繫結關係建立的時候,會在ServiceConnection物件的onServiceConnected()方法中收到Binder物件,然後通過該Binder物件的getService()方法獲得Service的引用,然後直接呼叫service的public方法。
在該示例中,在onStop()方法中對Service進行了unBind,client應該懂得在合適的時候對繫結的服務進行解綁,具體參加下面的附加部分。
使用Messenger
如果你的服務需要跨程序通訊,那麼可以使用Messenger來提供通訊的介面。
使用Messenger的步驟如下:
- 在Service中建立一個Handler物件,用來處理所有的請求。該Handler會持有一個IMessenger引用。
- 使用這個Handler建立一個Messenger物件。該Messenger也會持有同一個IMessenger的引用。
- 使用Messenger建立一個IBinder物件,並在onBind()方法中返回。
- client收到Binder物件後使用它建立一個Messenger物件,由於這個Messenger是使用Binder物件建立的,而Binder物件是Service中的Messenger建立的,而Service中的Messenger是使用Service中的Handler建立的,所以client中的Messenger和Service中的Handler就這樣建立了聯絡。
- 在client中使用Messenger物件傳送訊息。Service中的Handler就會收到訊息。準確來講是Handler的handleMessage()方法會收到訊息。
通過這種方式,client不會直接呼叫service的任何方法,而是通過message來傳遞資訊。下面是例子:
Server端:
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();
}
}
client端:
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;
}
}
}
這裡的關鍵點在onServiceConnected()方法中使用傳進來的IBinder物件new了一個Messenger,由於這個IBinder是和Service中的Handler有關係的,因此將client中的Messenger與service中的Handler建立了聯絡。然後就可以在client中通過Messenger傳送訊息了。
感覺Service端的Messenger只是起了個構建IBinder的作用,它自己本身並沒有傳到client中去。Client端是利用這個IBinder物件又new了一個Messenger。
上面的程式碼例子只能建立一個單向的通訊通道,即service無法向client返回結果。如果你需要service對client的請求返回一個結果的話,你需要在client中建立一個Messenger(注意:這個Messenger不是上面那個Messenger,下面有程式碼示例),然後傳送message的時候將這個Messenger物件封裝到message中去(Message有一個欄位為Messeneger型別,名為replyTo,即傳送的訊息中包含處理完這條訊息要向誰返回結果的資訊)。Service收到訊息後,需要返回結果的話,取出這個replyTo,使用它再向client發一條訊息。方式和client向serveice傳送訊息是一樣的。說再多還不如 show me the code!直接在上面的程式碼基礎上進行更改!
client端:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
int GIVE_ME_A_RESPONSE = 100;
int RESPONSE_FROM_SERVICE = 110;
Messenger activityMessenger = new Messenger(new ActivityHandler());
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/** this handler is created by the activity to handle response from service. */
class ActivityHandler extends Handler{
@Override
public void handlerMessage(Message msg){
switch(msg.what){
case RESPONSE_FROM_SERVICE:
Toast.makeText(ActivityMessenger.this, "response from service received",
Toast.LENGTH_SHORT).show();
break:
default:
super.handlerMessage(msg);
}
}
}
/**
* 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);//this Messenger is based on the Handler of 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();
}
}
public void sentMsgToServiceWithResponse(){
if (!mBound) return;
try {
Message msg = Message.obtain(null, GIVE_ME_A_RESPONSE)
msg.replyTo = activityMessenger;
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端:
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();
case ActivityMessenger.GIVE_ME_A_RESPONSE:
Message msg = Message.obtain(null, ActivityMessenger.RESPONSE_FROM_SERVICE);
msg.replyTo.send(msg);//retrieve the replyTo messenger to send back a response message
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();
}
}
仔細觀察上面的程式碼會發現,service返回結果給client的通訊方式和client傳送訊息給service的通訊方式完全相同!
與服務繫結
應用程式元件(client)通過呼叫bindService()方法與服務繫結。系統隨即呼叫服務的onBind()方法,返回一個IBinder給client用來通訊。
繫結的過程是非同步的,bindService()方法會立即返回,而不會等IBinder物件傳給client。要接收這個IBinder物件,必須在bindService()的時候傳入一個ServiceConnection物件。系統會回撥它的onServiceConnected()方法並傳入IBinder物件。
注意:只有Activity,Service,ContentProvider能夠和Service繫結,廣播接收器不能。
如上面的程式碼中所示,繫結一個服務需要如下幾個過程:
建立一個ServiceConnection物件,該物件必須實現下面兩個方法:
onServiceConnected()
系統通過回撥該方法來傳入IBinder物件
onServiceDisconnected()
當client與server之間的連結意外斷開的時候,系統會回撥該方法,比如當service崩潰或者被系統殺掉的時候。正常的解綁操作不會回撥該方法。
呼叫bindService(),將上一步的ServiceConnection物件傳進去。
當繫結關係建立後,在onServiceConnected()中使用IBinder介面來與服務通訊。
要與服務解綁,呼叫unbindService()
如果不解綁或者還沒來得及解綁,這個client就被app銷燬了,那麼將自動解綁。最好是在服務完成工作之後立即解綁,這樣可以關閉空閒服務。
關於bindService()的三個引數:
- Intent:上一篇關於服務的文章中也說過了,啟動服務不要使用隱式Intent。
- ServiceConnection:前面講了
- int:這個值決定了繫結後的服務要進行什麼操作,有好多種值,具體查閱API文件。