1. 程式人生 > >Android四大元件之Service第二章:Bound Service

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方法。

使用步驟

  1. 在service的內部生成一個Binder物件(成員變數),該Binder物件需要符合以下幾個特徵之一:
    • 該Binder物件本身包含一些public方法
    • 該Binder物件能夠返回其所在的Service物件,並且該Service物件中要包含public方法
    • 該Binder物件能夠返回Service中的另外一些包含public方法的物件
  2. 在onBind()方法中返回該Binder物件
  3. 在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的步驟如下:

  1. 在Service中建立一個Handler物件,用來處理所有的請求。該Handler會持有一個IMessenger引用。
  2. 使用這個Handler建立一個Messenger物件。該Messenger也會持有同一個IMessenger的引用。
  3. 使用Messenger建立一個IBinder物件,並在onBind()方法中返回。
  4. client收到Binder物件後使用它建立一個Messenger物件,由於這個Messenger是使用Binder物件建立的,而Binder物件是Service中的Messenger建立的,而Service中的Messenger是使用Service中的Handler建立的,所以client中的Messenger和Service中的Handler就這樣建立了聯絡。
  5. 在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繫結,廣播接收器不能。

如上面的程式碼中所示,繫結一個服務需要如下幾個過程:

  1. 建立一個ServiceConnection物件,該物件必須實現下面兩個方法:

    • onServiceConnected()

      系統通過回撥該方法來傳入IBinder物件

    • onServiceDisconnected()

      當client與server之間的連結意外斷開的時候,系統會回撥該方法,比如當service崩潰或者被系統殺掉的時候。正常的解綁操作不會回撥該方法。

  2. 呼叫bindService(),將上一步的ServiceConnection物件傳進去。

  3. 當繫結關係建立後,在onServiceConnected()中使用IBinder介面來與服務通訊。

  4. 要與服務解綁,呼叫unbindService()

    如果不解綁或者還沒來得及解綁,這個client就被app銷燬了,那麼將自動解綁。最好是在服務完成工作之後立即解綁,這樣可以關閉空閒服務。

關於bindService()的三個引數:

  • Intent:上一篇關於服務的文章中也說過了,啟動服務不要使用隱式Intent。
  • ServiceConnection:前面講了
  • int:這個值決定了繫結後的服務要進行什麼操作,有好多種值,具體查閱API文件。