1. 程式人生 > >Android-Service元件之Bound Service

Android-Service元件之Bound Service

bound service是工作在客戶端/服務端模式的service。bound service允許別的元件繫結到service上,然後傳送請求、接收響應、甚至是做跨程序的IPC操作。bound service只有在它為別的元件提供服務的時候才是存活的,並且不會在後臺無限的執行下去。

本文件會告訴你如何來建立一個bound service,包括如何從別的元件繫結到service上。但是,你首先要參考Service的文件來獲取關於service的一些知識,比如如何在service中傳送提醒,如何讓service在前臺執行,等等。

bound service基礎

bound service是service的一種實現,它允許別的元件繫結到它上面並和它做互動。為了給service提供繫結功能,你必須要實現onBind()回撥。這個方法會返回一個IBinder物件,它就定義了客戶端可以使用的跟服務端就行互動的介面。

客戶端可以通過呼叫bindService()繫結到service上。當客戶端想繫結的時候,它必須要提供一個ServiceConnection,ServiceConnection會監控跟service的連線。bindService()方法必須要立即返回而且沒有返回值,但是,android系統會呼叫ServiceConnection的onServiceConnected()來建立service和客戶端之間的連線,然後傳遞進去客戶端可以用來跟服務端通訊的IBinder。

多個客戶端可以同時連線到一個service上,但是,只有當第一個客戶端繫結到service的時候,系統才會去呼叫service的onBind()方法來檢索出IBinder物件,後續客戶端繫結的時候,系統並不會再次呼叫onBind(),而是把檢索出的那個IBinder物件傳遞過去。

當最後一個客戶端從service解綁以後,系統會銷燬掉service(除非service同時也是用startService()啟動的)

當你實現你的bound service的時候,最重要的事情就是定義onBind()回撥中返回的IBinder介面。有幾種不同的定義service的IBinder介面的方式,下面的章節將會介紹這幾種技術。

如何建立Bound Service

當建立一個提供繫結功能的service的時候,必須要給客戶端提供一個可以跟service進行互動的程式設計介面,有三種定義介面的方式:

繼承Binder類
如果service是你的應用私有的,並且是和客戶端執行在同一個程序中(典型的情況),你應該通過繼承Binder類來建立介面,然後在onBind()中返回它的例項。客戶端收到Binder以後,可以直接使用它訪問Binder的public方法甚至是service的方法。

當service僅僅是你自己應用的一個後臺工作者的情況下,這是比較合適的方式。之所以不用這種方式的唯一的原因是因為你的service要被別的應用或者是跨程序來使用。

使用Messenger

如果你的介面要跨程序使用,可以用Messenger來建立service的介面。這種方式下,service定義了一個Handler來處理不同型別的Message物件。Handler是Messenger的基礎,它可以在多個客戶端之間共享同一個IBinder,允許客戶端使用Message給service傳送請求。此外,客戶端可以定義它自己的Messenger,然後service就可以把message傳送給客戶端。

這是最簡單的實現IPC的方式,因為Messenger會把所有的請求入隊到同一個執行緒中,所以不需要把你的service設計成執行緒安全的。

使用AIDL

AIDL

AIDL可以將物件序列化成作業系統可以理解的基本資料型別然後反序列化到別的程序中來做IPC。前面介紹的Messenger實際上底層結構也是基於AIDL的。正如前面提到的那樣,Messenger會把客戶端的請求壓到同一個執行緒中,所以service一次只能接受到一個請求。如果你想讓你的service可以同時併發的處理多個請求,那麼就可以使用AIDL。在這種情況下,service就必須要能夠安全的支援多執行緒。

要直接使用AIDL,必須要建立一個定義了程式設計介面的.aidl檔案。Android SDK工具會使用這個檔案來產生一個實現了這些介面的抽象類來處理IPC,要在你的service中對這個抽象類做實現。

注意:大多數應用不應該使用AIDL來建立bound service,因為它需要多執行緒支援並且會導致非常複雜的實現。基於此,對於大多數應用來說AIDL都不適用,因此本文件不會介紹如何在service中來使用它。如果你確定你需要直接使用AIDL,參考AIDL的文件(http://developer.android.com/guide/components/aidl.html)。

繼承Binder類

如果你的service只是在本應用使用而且不需要跨程序的操作,那麼你可以實現你自己的Binder類,它可以讓客戶端直接訪問service的公開的方法。

注意:只有當客戶端和service是處於同一個應用的同一個程序中的時候才可以這麼用,大多數情況下也正是如此。比如,音樂應用就和適合這種方式,因為它需要把Activity繫結到在後臺播放音樂的它自己的service上。

下面是具體的步驟:
(1)在你的service中,建立Binder的例項:
要麼包含客戶端可以呼叫的public方法
要麼返回當前的service例項,它有客戶端可以呼叫的public方法
要麼返回service內部的其他類的例項,這個類也有客戶端可以呼叫的public方法。
(2)在onBind()回撥中返回Binder的例項。
(3)客戶端從onServiceConnected()回撥中接收Binder例項,使用Binder提供的方法呼叫bound service。

注意:service和客戶端必須在同一個應用的原因是客戶端可以正確的轉化返回的物件,並且能正確的呼叫service的api。service和客戶端必須在同一個程序的原因是因為這種方式不能做跨程序的序列化。

比如:下面是一個可以讓客戶端通過Binder訪問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(100);
    }
}
LocalBinder給客戶端提供了用來檢索出當前LocalService例項的getService()方法。這就允許客戶端呼叫service當中的public的方法。比如客戶端可以呼叫service中的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()回撥繫結到service上。下面的章節提供了更多關於繫結到service的知識。

注意:例子中並沒有明確的解綁service,但是實際中所有的客戶端都應該在適當的時候與service解綁(比如當Activity進入pause的時候)。

使用Messenger

如果你的service需要跟別的程序進行通訊,那麼你可以使用Messenger來給service提供介面。這種方式允許不使用AIDL就可以做跨程序的IPC。

使用Messenger的步驟:
(1)service實現一個handler,handler接收客戶端的回撥。
(2)handler用來建立Messenger物件(它有對handler的引用)
(3)Messenger會建立service在onBind()中返回給客戶端的IBinder。
(4)客戶端使用IBinder來例項化Messenger(引用了service的handler),客戶端使用它來給service傳送訊息。
(5)service在handler的handleMessage()中收到訊息。

這種方式下,客戶端不需要呼叫service的方法,相反,客戶端想service傳送訊息,service通過handler接收訊息。

下面是一個使用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()方法也是service收到訊息並根據訊息的what成員決定如何處理訊息的地方。

客戶端需要做的就是建立一個從service返回的基於IBinder的Messenger,然後使用send()傳送訊息。比如下面是一個簡單的繫結到service的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, 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;
        }
    }
}

注意到這個例子中並沒有展示service是如何給客戶端響應的。如果你希望service給出相應,那麼你需要在客戶端也建立一個Messenger。然後,當客戶端收到onServiceConnected()回撥的時候,它可以給service傳送一個訊息,在send()方法的引數訊息中的replyTo就是客戶端的Messenger。

可以看下MessengerService.java(service)和MessengerServiceActivities.java(client)的例子是如何提供雙向訊息的。

package com.example.android.apis.app;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;


import java.util.ArrayList;


// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import com.example.android.apis.R;
import com.example.android.apis.app.RemoteService.Controller;


/**
 * This is an example of implementing an application service that uses the
 * {@link Messenger} class for communicating with clients.  This allows for
 * remote interaction with a service, without needing to define an AIDL
 * interface.
 *
 * <p>Notice the use of the {@link NotificationManager} when interesting things
 * happen in the service.  This is generally how background services should
 * interact with the user, rather than doing something more disruptive such as
 * calling startActivity().
 */


public class MessengerService extends Service {
    /** For showing and hiding our notification. */
    NotificationManager mNM;
    /** Keeps track of all current registered clients. */
    ArrayList<Messenger> mClients = new ArrayList<Messenger>();
    /** Holds last value set by a client. */
    int mValue = 0;
    
    /**
     * Command to the service to register a client, receiving callbacks
     * from the service.  The Message's replyTo field must be a Messenger of
     * the client where callbacks should be sent.
     */
    static final int MSG_REGISTER_CLIENT = 1;
    
    /**
     * Command to the service to unregister a client, ot stop receiving callbacks
     * from the service.  The Message's replyTo field must be a Messenger of
     * the client as previously given with MSG_REGISTER_CLIENT.
     */
    static final int MSG_UNREGISTER_CLIENT = 2;
    
    /**
     * Command to service to set a new value.  This can be sent to the
     * service to supply a new value, and will be sent by the service to
     * any registered clients with the new value.
     */
    static final int MSG_SET_VALUE = 3;
    
    /**
     * Handler of incoming messages from clients.
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    mClients.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    break;
                case MSG_SET_VALUE:
                    mValue = msg.arg1;
                    for (int i=mClients.size()-1; i>=0; i--) {
                        try {
                            mClients.get(i).send(Message.obtain(null,
                                    MSG_SET_VALUE, mValue, 0));
                        } catch (RemoteException e) {
                            // The client is dead.  Remove it from the list;
                            // we are going through the list from back to front
                            // so this is safe to do inside the loop.
                            mClients.remove(i);
                        }
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    
    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());
    
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);


        // Display a notification about us starting.
        showNotification();
    }


    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(R.string.remote_service_started);


        // Tell the user we stopped.
        Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
    }
    
    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }


    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.remote_service_started);


        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.stat_sample, text,
                System.currentTimeMillis());


        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, Controller.class), 0);


        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.remote_service_label),
                       text, contentIntent);


        // Send the notification.
        // We use a string id because it is a unique number.  We use it later to cancel.
        mNM.notify(R.string.remote_service_started, notification);
    }
}

package com.example.android.apis.app;


import com.example.android.apis.R;
import com.example.android.apis.app.LocalServiceActivities.Binding;


import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


public class MessengerServiceActivities {
    /**
     * Example of binding and unbinding to the remote service.
     * This demonstrates the implementation of a service which the client will
     * bind to, interacting with it through an aidl interface.</p>
     * 
     * <p>Note that this is implemented as an inner class only keep the sample
     * all together; typically this code would appear in some separate class.
     */
    public static class Binding extends Activity {


        /** Messenger for communicating with service. */
        Messenger mService = null;
        /** Flag indicating whether we have called bind on the service. */
        boolean mIsBound;
        /** Some text view we are using to show state information. */
        TextView mCallbackText;
        
        /**
         * Handler of incoming messages from service.
         */
        class IncomingHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MessengerService.MSG_SET_VALUE:
                        mCallbackText.setText("Received from service: " + msg.arg1);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
        
        /**
         * Target we publish for clients to send messages to IncomingHandler.
         */
        final Messenger mMessenger = new Messenger(new IncomingHandler());
        
        /**
         * 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 service object we can use to
                // interact with the service.  We are communicating with our
                // service through an IDL interface, so get a client-side
                // representation of that from the raw service object.
                mService = new Messenger(service);
                mCallbackText.setText("Attached.");


                // We want to monitor the service for as long as we are
                // connected to it.
                try {
                    Message msg = Message.obtain(null,
                            MessengerService.MSG_REGISTER_CLIENT);
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                    
                    // Give it some value as an example.
                    msg = Message.obtain(null,
                            MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
                    mService.send(msg);
                } catch (RemoteException e) {
                    // In this case the service has crashed before we could even
                    // do anything with it; we can count on soon being
                    // disconnected (and then reconnected if it can be restarted)
                    // so there is no need to do anything here.
                }
                
                // As part of the sample, tell the user what happened.
                Toast.makeText(Binding.this, R.string.remote_service_connected,
                        Toast.LENGTH_SHORT).show();
            }


            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;
                mCallbackText.setText("Disconnected.");


                // As part of the sample, tell the user what happened.
                Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                        Toast.LENGTH_SHORT).show();
            }
        };
        
        void doBindService() {
            // Establish a connection with the service.  We use an explicit
            // class name because there is no reason to be able to let other
            // applications replace our component.
            bindService(new Intent(Binding.this, 
                    MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
        
        void doUnbindService() {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        Message msg = Message.obtain(null,
                                MessengerService.MSG_UNREGISTER_CLIENT);
                        msg.replyTo = mMessenger;
                        mService.send(msg);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }
                
                // Detach our existing connection.
                unbindService(mConnection);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }


        
        /**
         * Standard initialization of this activity.  Set up the UI, then wait
         * for the user to poke it before doing anything.
         */
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);


            setContentView(R.layout.messenger_service_binding);


            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.bind);
            button.setOnClickListener(mBindListener);
            button = (Button)findViewById(R.id.unbind);
            button.setOnClickListener(mUnbindListener);
            
            mCallbackText = (TextView)findViewById(R.id.callback);
            mCallbackText.setText("Not attached.");
        }


        private OnClickListener mBindListener = new OnClickListener() {
            public void onClick(View v) {
                doBindService();
            }
        };


        private OnClickListener mUnbindListener = new OnClickListener() {
            public void onClick(View v) {
                doUnbindService();
            }
        };
    }
}

繫結到Service

客戶端可以呼叫bindService()繫結到service上。然後系統會呼叫service的onBind()方法,這個方法會返回用來跟service互動的IBinder物件。


繫結是非同步的,bindService()會立即返回,而且並不會給客戶端返回IBinder。為了能收到IBinder,客戶端必須要建立ServiceConnection的例項,然後把它傳遞給bindService()。ServiceConnection包含了一個回撥,系統會呼叫這個回撥並傳遞IBinder。

注意:只有activity, service, 和 content provider才可以繫結到service上,不能從廣播接收器中繫結到service上。

所以為了從客戶端繫結到service,必須要:

(1)實現ServiceConnection.你的實現必須要覆蓋兩個回撥方法:
一個是onServiceConnected():系統會呼叫這個方法來傳遞從service的onBind()中返回的IBinder。
一個是onServiceDisconnected():當連線到service的連線以外的掛掉以後,系統會呼叫這個方法,比如:service崩潰或者被殺掉。
(2)呼叫bindService(),把ServiceConnection傳遞進去
(3)當系統呼叫你的onServiceConnected()回撥的時候,你就可以開始使用介面的方法來呼叫service了。
(4)為了跟service解綁,可以呼叫unbindService().當你的客戶端銷燬以後,它會service上解綁,但是當你跟service互動完成以後或者你的Activity進入pause以後,
你總是應該手動的進行解綁,這樣才可以讓service在不使用的時候進行關閉(下面會討論繫結和解綁的時機)。

舉個例子,下面的程式碼片段通過繼承Binder的方式把客戶端連線到service上,因此客戶端必須要做的事情就是把返回的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()中來繫結到service上,就像:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
(1)bindService()的第一個引數是一個intent,這個intent會明確的指明瞭要繫結的service的名字(當然intent也可以是隱式的)。
(2)第二個引數是ServiceConnection
(3)第三個引數是繫結的一個標識。一般來說是BIND_AUTO_CREATE,這個引數在service不alive的時候會建立service。其他可選的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者是0。

其他的注意事項

關於繫結到service的一些很重要的事項:

(1)你應該總是要捕獲DeadObjectException異常,當連線壞掉以後就會丟擲這個異常。這是遠端方法可以丟擲的唯一的異常。
(2)Objects are reference counted across processes.(啥意思?)
(3)你應該讓繫結和解綁匹配上客戶端的生命週期的開始和結束。比如:
如果你只是想讓Activity處於可見狀態的時候才可以跟service進行互動,那麼你就應該在onStart()進行繫結,在onStop()進行解綁。
如果你想讓Activity就算是stop處於後臺的時候也能接收service的響應,那麼你可以在onCreate()進行解綁,在onDestroy()進行解綁。
這時候要注意你的Activity在它的整個的生命週期(甚至是後臺)中都需要使用service,因此如果service是在別的程序中,
那麼你就增加了那個程序的權重,那麼系統就以更大的可能性殺掉這個程序。

注意:一般不應該在Activity的onResume()和onPause()做繫結和解綁,因為每一個生命週期變化中都會呼叫這兩個回撥,你應該讓變化中的繫結和解綁的操作最少。
同時,如果你的應用中有多個Activity繫結到了同一個service上,如果其中兩個Activity有生命週期變遷的時候,service可能會被銷燬然後重建,因為當前的Activity會做解綁(pause),新的Activity會做繫結(resume)(Activity在變遷過程中生命週期的變化在Activity的文件中有說明)。

要獲取更多例子程式碼,檢視如何繫結到service上,可以參考ApiDemos裡面的RemoteService.java。

package com.example.android.apis.app;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import com.example.android.apis.R;


/**
 * This is an example of implementing an application service that runs in a
 * different process than the application.  Because it can be in another
 * process, we must use IPC to interact with it.  The
 * {@link Controller} and {@link Binding} classes
 * show how to interact with the service.
 * 
 * <p>Note that most applications <strong>do not</strong> need to deal with
 * the complexity shown here.  If your application simply has a service
 * running in its own process, the {@link LocalService} sample shows a much
 * simpler way to interact with it.
 */
public class RemoteService extends Service {
    /**
     * This is a list of callbacks that have been registered with the
     * service.  Note that this is package scoped (instead of private) so
     * that it can be accessed more efficiently from inner classes.
     */
    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks
            = new RemoteCallbackList<IRemoteServiceCallback>();
    
    int mValue = 0;
    NotificationManager mNM;
    
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);


        // Display a notification about us starting.
        showNotification();
        
        // While this service is running, it will continually increment a
        // number.  Send the first message that is used to perform the
        // increment.
        mHandler.sendEmptyMessage(REPORT_MSG);
    }


    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(R.string.remote_service_started);


        // Tell the user we stopped.
        Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
        
        // Unregister all callbacks.
        mCallbacks.kill();
        
        // Remove the next pending message to increment the counter, stopping
        // the increment loop.
        mHandler.removeMessages(REPORT_MSG);
    }
    


    @Override
    public IBinder onBind(Intent intent) {
        // Select the interface to return.  If your service only implements
        // a single interface, you can just return it here without checking
        // the Intent.
        if (IRemoteService.class.getName().equals(intent.getAction())) {
            return mBinder;
        }
        if (ISecondary.class.getName().equals(intent.getAction())) {
            return mSecondaryBinder;
        }
        return null;
    }


    /**
     * The IRemoteInterface is defined through IDL
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public void registerCallback(IRemoteServiceCallback cb) {
            if (cb != null) mCallbacks.register(cb);
        }
        public void unregisterCallback(IRemoteServiceCallback cb) {
            if (cb != null) mCallbacks.unregister(cb);
        }
    };


    /**
     * A secondary interface to the service.
     */
    private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {
        public int getPid() {
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                float aFloat, double aDouble, String aString) {
        }
    };


    
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show();
    }
    
    private static final int REPORT_MSG = 1;


    /**
     * Our Handler used to execute operations on the main thread.  This is used
     * to schedule increments of our value.
     */
    private final Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                
                // It is time to bump the value!
                case REPORT_MSG: {
                    // Up it goes.
                    int value = ++mValue;
                    
                    // Broadcast to all clients the new value.
                    final int N = mCallbacks.beginBroadcast();
                    for (int i=0; i<N; i++) {
                        try {
                            mCallbacks.getBroadcastItem(i).valueChanged(value);
                        } catch (RemoteException e) {
                            // The RemoteCallbackList will take care of removing
                            // the dead object for us.
                        }
                    }
                    mCallbacks.finishBroadcast();
                    
                    // Repeat every 1 second.
                    sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000);
                } break;
                default:
                    super.handleMessage(msg);
            }
        }
    };


    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.remote_service_started);


        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.stat_sample, text,
                System.currentTimeMillis());


        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, Controller.class), 0);


        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.remote_service_label),
                       text, contentIntent);


        // Send the notification.
        // We use a string id because it is a unique number.  We use it later to cancel.
        mNM.notify(R.string.remote_service_started, notification);
    }
    
    // ----------------------------------------------------------------------
    
    /**
     * <p>Example of explicitly starting and stopping the remove service.
     * This demonstrates the implementation of a service that runs in a different
     * process than the rest of the application, which is explicitly started and stopped
     * as desired.</p>
     * 
     * <p>Note that this is implemented as an inner class only keep the sample
     * all together; typically this code would appear in some separate class.
     */
    public static class Controller extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);


            setContentView(R.layout.remote_service_controller);


            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.start);
            button.setOnClickListener(mStartListener);
            button = (Button)findViewById(R.id.stop);
            button.setOnClickListener(mStopListener);
        }


        private OnClickListener mStartListener = new OnClickListener() {
            public void onClick(View v) {
                // Make sure the service is started.  It will continue running
                // until someone calls stopService().
                // We use an action code here, instead of explictly supplying
                // the component name, so that other packages can replace
                // the service.
                startService(new Intent(
                        "com.example.android.apis.app.REMOTE_SERVICE"));
            }
        };


        private OnClickListener mStopListener = new OnClickListener() {
            public void onClick(View v) {
                // Cancel a previous call to startService().  Note that the
                // service will not actually stop at this point if there are
                // still bound clients.
                stopService(new Intent(
                        "com.example.android.apis.app.REMOTE_SERVICE"));
            }
        };
    }
    
    // ----------------------------------------------------------------------
    
    /**
     * Example of binding and unbinding to the remote service.
     * This demonstrates the implementation of a service which the client will
     * bind to, interacting with it through an aidl interface.</p>
     * 
     * <p>Note that this is implemented as an inner class only keep the sample
     * all together; typically this code would appear in some separate class.
     */


    public static class Binding extends Activity {
        /** The primary interface we will be calling on the service. */
        IRemoteService mService = null;
        /** Another interface we use on the service. */
        ISecondary mSecondaryService = null;
        
        Button mKillButton;
        TextView mCallbackText;


        private boolean mIsBound;


        /**
         * Standard initialization of this activity.  Set up the UI, then wait
         * for the user to poke it before doing anything.
         */
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);


            setContentView(R.layout.remote_service_binding);


            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.bind);
            button.setOnClickListener(mBindListener);
            button = (Button)findViewById(R.id.unbind);
            button.setOnClickListener(mUnbindListener);
            mKillButton = (Button)findViewById(R.id.kill);
            mKillButton.setOnClickListener(mKillListener);
            mKillButton.setEnabled(false);
            
            mCallbackText = (TextView)findViewById(R.id.callback);
            mCallbackText.setText("Not attached.");
        }


        /**
         * 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 service object we can use to
                // interact with the service.  We are communicating with our
                // service through an IDL interface, so get a client-side
                // representation of that from the raw service object.
                mService = IRemoteService.Stub.asInterface(service);
                mKillButton.setEnabled(true);
                mCallbackText.setText("Attached.");


                // We want to monitor the service for as long as we are
                // connected to it.
                try {
                    mService.registerCallback(mCallback);
                } catch (RemoteException e) {
                    // In this case the service has crashed before we could even
                    // do anything with it; we can count on soon being
                    // disconnected (and then reconnected if it can be restarted)
                    // so there is no need to do anything here.
                }
                
                // As part of the sample, tell the user what happened.
                Toast.makeText(Binding.this, R.string.remote_service_connected,
                        Toast.LENGTH_SHORT).show();
            }


            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;
                mKillButton.setEnabled(false);
                mCallbackText.setText("Disconnected.");


                // As part of the sample, tell the user what happened.
                Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                        Toast.LENGTH_SHORT).show();
            }
        };


        /**
         * Class for interacting with the secondary interface of the service.
         */
        private ServiceConnection mSecondaryConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className,
                    IBinder service) {
                // Connecting to a secondary interface is the same as any
                // other interface.
                mSecondaryService = ISecondary.Stub.asInterface(service);
                mKillButton.setEnabled(true);
            }


            public void onServiceDisconnected(ComponentName className) {
                mSecondaryService = null;
                mKillButton.setEnabled(false);
            }
        };


        private OnClickListener mBindListener = new OnClickListener() {
            public void onClick(View v) {
                // Establish a couple connections with the service, binding
                // by interface names.  This allows other applications to be
                // installed that replace the remote service by implementing
                // the same interface.
                bindService(new Intent(IRemoteService.class.getName()),
                        mConnection, Context.BIND_AUTO_CREATE);
                bindService(new Intent(ISecondary.class.getName()),
                        mSecondaryConnection, Context.BIND_AUTO_CREATE);
                mIsBound = true;
                mCallbackText.setText("Binding.");
            }
        };


        private OnClickListener mUnbindListener = new OnClickListener() {
            public void onClick(View v) {
                if (mIsBound) {
                    // If we have received the service, and hence registered with
                    // it, then now is the time to unregister.
                    if (mService != null) {
                        try {
                            mService.unregisterCallback(mCallback);
                        } catch (RemoteException e) {
                            // There is nothing special we need to do if the service
                            // has crashed.
                        }
                    }
                    
                    // Detach our existing connection.
                    unbindService(mConnection);
                    unbindService(mSecondaryConnection);
                    mKillButton.setEnabled(false);
                    mIsBound = false;
                    mCallbackText.setText("Unbinding.");
                }
            }
        };


        private OnClickListener mKillListener = new OnClickListener() {
            public void onClick(View v) {
                // To kill the process hosting our service, we need to know its
                // PID.  Conveniently our service has a call that will return
                // to us that information.
                if (mSecondaryService != null) {
                    try {
                        int pid = mSecondaryService.getPid();
                        // Note that, though this API allows us to request to
                        // kill any process based on its PID, the kernel will
                        // still impose standard restrictions on which PIDs you
                        // are actually able to kill.  Typically this means only
                        // the process running your application and any additional
                        // processes created by that app as shown here; packages
                        // sharing a common UID will also be able to kill each
                        // other's processes.
                        Process.killProcess(pid);
                        mCallbackText.setText("Killed service process.");
                    } catch (RemoteException ex) {
                        // Recover gracefully from the process hosting the
                        // server dying.
                        // Just for purposes of the sample, put up a notification.
                        Toast.makeText(Binding.this,
                                R.string.remote_call_failed,
                                Toast.LENGTH_SHORT).show();
                    }
                }
            }
        };
        
        // ----------------------------------------------------------------------
        // Code showing how to deal with callbacks.
        // ----------------------------------------------------------------------
        
        /**
         * This implementation is used to receive callbacks from the remote
         * service.
         */
        private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
            /**
             * This is called by the remote service regularly to tell us about
             * new values.  Note that IPC calls are dispatched through a thread
             * pool running in each process, so the code executing here will
             * NOT be running in our main thread like most other things -- so,
             * to update the UI, we need to use a Handler to hop over there.
             */
            public void valueChanged(int value) {
                mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
            }
        };
        
        private static final int BUMP_MSG = 1;
        
        private Handler mHandler = new Handler() {
            @Override public void handleMessage(Message msg) {
                switch (msg.what) {
                    case BUMP_MSG:
                        mCallbackText.setText("Received from service: " + msg.arg1);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
            
        };
    }




    // ----------------------------------------------------------------------


    /**
     * Examples of behavior of different bind flags.</p>
     */


    public static class BindingOptions extends Activity {
        ServiceConnection mCurConnection;
        TextView mCallbackText;


        class MyServiceConnection implements ServiceConnection {
            final boolean mUnbindOnDisconnect;


            public MyServiceConnection() {
                mUnbindOnDisconnect = false;
            }


            public MyServiceConnection(boolean unbindOnDisconnect) {
                mUnbindOnDisconnect = unbindOnDisconnect;
            }


            public void onServiceConnected(ComponentName className,
                    IBinder service) {
                if (mCurConnection != this) {
                    return;
                }
                mCallbackText.setText("Attached.");
                Toast.makeText(BindingOptions.this, R.string.remote_service_connected,
                        Toast.LENGTH_SHORT).show();
            }


            public void onServiceDisconnected(ComponentName className) {
                if (mCurConnection != this) {
                    return;
                }
                mCallbackText.setText("Disconnected.");
                Toast.makeText(BindingOptions.this, R.string.remote_service_disconnected,
                        Toast.LENGTH_SHORT).show();
                if (mUnbindOnDisconnect) {
                    unbindService(this);
                    mCurConnection = null;
                    Toast.makeText(BindingOptions.this, R.string.remote_service_unbind_disconn,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }


        /**
         * Standard initialization of this activity.  Set up the UI, then wait
         * for the user to poke it before doing anything.
         */
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);


            setContentView(R.layout.remote_binding_options);


            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.bind_normal);
            button.setOnClickListener(mBindNormalListener);
            button = (Button)findViewById(R.id.bind_not_foreground);
            button.setOnClickListener(mBindNotForegroundListener);
            button = (Button)findViewById(R.id.bind_above_client);
            button.setOnClickListener(mBindAboveClientListener);
            button = (Button)findViewById(R.id.bind_allow_oom);
            button.setOnClickListener(mBindAllowOomListener);
            button = (Button)findViewById(R.id.bind_waive_priority);
            button.setOnClickListener(mBindWaivePriorityListener);
            button = (Button)findViewById(R.id.bind_important);
            button.setOnClickListener(mBindImportantListener);
            button = (Button)findViewById(R.id.bind_with_activity);
            button.setOnClickListener(mBindWithActivityListener);
            button = (Button)findViewById(R.id.unbind);
            button.setOnClickListener(mUnbindListener);


            mCallbackText = (TextView)findViewById(R.id.callback);
            mCallbackText.setText("Not attached.");
        }


        private OnClickListener mBindNormalListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindNotForegroundListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindAboveClientListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindAllowOomListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindWaivePriorityListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection(true);
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindImportantListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mBindWithActivityListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
                ServiceConnection conn = new MyServiceConnection();
                if (bindService(new Intent(IRemoteService.class.getName()),
                        conn, Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY
                        | Context.BIND_WAIVE_PRIORITY)) {
                    mCurConnection = conn;
                }
            }
        };


        private OnClickListener mUnbindListener = new OnClickListener() {
            public void onClick(View v) {
                if (mCurConnection != null) {
                    unbindService(mCurConnection);
                    mCurConnection = null;
                }
            }
        };
    }
}

管理Bound Service的生命週期

當service從所有的客戶端解綁以後,android系統會銷燬掉service(除非service也是用onStartCommand()啟動的)如果service是一個純的bound service的情況下你不需要手動管理service的生命週期,系統會根據service是否繫結到了客戶端來自動的進行管理。

但是,如果你選擇實現onStartCommand()這個回撥,那麼你必須要明確的停掉service。因為不管它是否繫結到了客戶端上,現在service都會被認為是started,這種情況下,service會一直執行下去,除非service呼叫stopSelf()把自己停止掉或者是別的元件呼叫stopService()把它停掉,

此外,如果你的service是started並且也接受繫結,那麼當系統呼叫onUnbind()的時候,如果你希望下次有客戶端繫結到service的時候你可以收到onRebind()呼叫的話(而不是收到onbind()回撥),你可以在onUnbind()中返回true。onRebind()返回空,但是客戶端仍然可以在onServiceConnected()中收到IBinder。下面的圖1展示了這種型別的service的生命週期。