Skip to content

Most visited

Recently visited

navigation

繫結服務

繫結服務是主從介面中的伺服器。繫結服務讓元件 (例如 Activity) 可以繫結至服務、傳送要求、接收回應,甚至執行處理程序間通訊 (IPC)。 繫結服務通常只會在服務另一個應用程式元件時才存在,而且不會在背景中一直執行。

本文會告訴您如何建立繫結服務,包括如何從其他應用程式元件繫結至服務。 不過,如需關於服務的一般其他資訊,可參閱服務文件,例如如何從服務傳遞通知、設定服務在前景執行等等。

基本概念

繫結服務是 Service 類別的實作,允許其他應用程式繫結至此實作,並與之互動。 若服務要要提供繫結功能,您必須實作 onBind() 回呼方法。 此方法會傳回 IBinder 物件,其中定義程式設計介面。用戶端可以使用此介面與服務互動。

用戶端可以透過呼叫 bindService() 繫結至服務。這麼做時,用戶端必須實作 ServiceConnection 以監視與服務之間的連線狀況。bindService() 方法會立即傳回 (不含值),當 Android 系統在用戶端和服務之間建立連線時,會呼叫 onServiceConnected() (位於 ServiceConnection) 以傳遞 IBinder (用戶端可以用來與服務溝通)。

多個用戶端可以同時連線至該服務。不過,系統只會在用戶端第一次繫結時,呼叫服務的 onBind() 方法,以擷取 IBinder。 然後,系統會將同一個 IBinder 傳遞給其他任何繫結的用戶端,不會再次呼叫 onBind()

最後一個用戶端從服務解除繫結時,系統會終結服務 (除非服務也是由 startService() 所啟動)。

實作繫結服務時,最重要的部分是定義您的 onBind() 回呼方法傳回的介面。 您可以使用幾種不同的方式來定義服務的 IBinder 介面,以下小節將討論其中的每一種技術。

建立已繫結的服務

建立提供繫結功能的服務時,您必須提供 IBinder 程式設計介面。用戶端可以使用此介面與服務互動。 定義介面有三種方式:

延伸 Binder 類別
如果您的服務只讓您自己的應用程式使用,而且跟用戶端執行在同一個處理程序 (此作法很常見),則應該要透過延伸 Binder 類別,並從 onBind() 傳回此類別的執行個體來建立介面。 用戶端接收 Binder 後,可以用它直接存取 Binder 實作或 Service 提供的公用方法。

若您的服務只是您自己應用程式的背景工作者,建議使用此方式。 除非您的服務是由其他應用程式或跨個別處理程序使用,才不用以此方式建立自己的介面。

使用 Messenger
如果您的介面需要跨不同處理程序運作,則可以建立內含 Messenger 服務的介面。 此服務定義了回應不同類型 Message 物件的 Handler。 這個 HandlerMessenger 的基礎,之後可以與用戶端分享 IBinder,讓用戶端使用 Message 物件傳送命令給此服務。 此外,用戶端可以定義專屬的 Messenger,服務就可以傳回訊息。

這是處理程序間通訊 (IPC) 最簡單的執行方式,因為 Messenger 會將所有要求都排列到單一個執行緒,因此,就不用將服務設計成執行緒安全的形式。

使用 AIDL
AIDL (Android 介面定義語言) 的工作是將物件分解為作業系統瞭解的始類型,然後將這些原始類型在各個處理程序間進行封送,以執行 IPC。先前使用 Messenger 的技術,實際上就是以 AIDL 作為底層結構。 如上所述,Messenger 會在單一執行緒中建立所有用戶端要求的佇列,所以服務一次會接收一個要求。 不過,如果您要讓服務可以同時處理多個要求,則可以直接使用 AIDL。 在此情況下,您的服務必須具備多執行緒的功能,而且是以執行緒安全的形式建置。

如要直接使用 AIDL,您必須建立 .aidl 檔案,並在其中定義程式設計介面。 Android SDK 工具會使用此檔案產生一個抽象類別,以便實作介面並處理 IPC。您就可以在服務內加以延伸。

注意:大部分應用程式不應使用 AIDL 建立繫結服務,若自行建立的話,就需要實作多執行緒功能,可能會導致更複雜的實作。 因此,AIDL 不適用於大部分應用程式,而且本文不會討論如何在您的服務中使用 AIDL。 如果您確定需要直接使用 AIDL,請參閱 AIDL 文件。

延伸 Binder 類別

如果您的服務只會在本機應用程式使用,而且不需要跨處理程序運作,則可以實作您自己的 Binder 類別,讓用戶端直接存取服務中的公用方法。

注意:用戶端和服務都位於相同應用程式和處理程序時才適用,這也是最常見的情況。 例如,需要將 Activity 繫結到其專屬服務 (在背景播放音樂)的音樂應用程式,就很適合。

設定的方式如下:

  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 並呼叫 getRandomNumber()的 Activity:

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() 回呼,繫結至服務。下一節提供關於繫結至服務處理程序的詳細資訊。

注意:上述範例未明確從服務解除繫結,但所有用戶端都應該在適當時間解除繫結 (例如,Activity 暫停時)。

如要取得更多範例程式碼,請參閱 ApiDemos 中的 LocalService.java 類別和 LocalServiceActivities.java 類別。

使用 Messenger

如果服務要和遠端處理程序溝通,則可以使用 Messenger 為您的服務提供介面。此技術讓您不需要使用 AIDL,就可以執行處理程序間通訊 (IPC)。

以下是使用 Messenger 的摘要:

這樣一來,用戶端就不需要呼叫服務的任何「方法」。用戶端只要傳遞「訊息」(Message 物件),服務就會在其 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() 方法是服務接收傳入 Message 的位置,也是根據 what 成員,決定後續執行動作的位置。

用戶端只需要根據服務傳回的 IBinder,建立 Messenger,然後使用 send() 傳送訊息。例如,以下的簡單 Activity 會繫結至服務,然後將 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() 回呼時,會傳送 Message 給服務,其中包括用戶端的 Messenger (位於 send() 方法的 replyTo 參數中)。

您可以在 MessengerService.java (服務) 和 MessengerServiceActivities.java (用戶端) 的範例中看到如何進行雙向傳訊的例子。

繫結至服務

應用程式元件 (用戶端) 可以透過呼叫 bindService(),繫結至服務。然後,Android 系統會呼叫服務的 onBind() 方法 (此方法傳回 IBinder 以便與服務互動)。

繫結為非同步。bindService() 會立即傳回,但「不會」將 IBinder 傳回給用戶端。 如要接收 IBinder,用戶端必須建立 ServiceConnection 的執行個體,然後將此執行個體傳送給 bindService()ServiceConnection 內含回呼方法,系統會呼叫此方法以傳遞 IBinder

注意:只有 Activity、服務以及內容提供者可以繫結至服務 — 您不能從廣播接收者繫結至服務。

因此,如要從用戶端繫結至服務,必須符合下列條件:

  1. 實作 ServiceConnection

    實作中必須覆寫兩個回呼方法:

    onServiceConnected()
    系統會呼叫此方法來傳遞 IBinder (由服務的 onBind() 方法所傳回)。
    onServiceDisconnected()
    與服務之間的連線突然遺失時 (例如服務當機或遭到終止時),Android 系統會呼收此方法。 用戶端解除繫結時,「不會」呼叫此方法。
  2. 呼叫會傳送 ServiceConnection 實作的 bindService()
  3. 系統呼叫 onServiceConnected() 回呼方法時,您就可以使用介面定義的方法,開始呼叫服務。
  4. 如要與服務中斷連線,請呼叫 unbindService()

    用戶端遭到終結時,會與服務解除繫結。不過,您應該要在與服務完成互動時,或者 Activity 暫停,要讓服務未使用時可以加以關閉的情況下,一定要解除繫結。 (以下將有繫結和解除繫結適當時機的詳細討論)。

例如,下列程式碼片段會透過延伸 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);

其他注意事項

以下是關於繫結至服務的一些重要注意事項:

如要取得如何繫結至服務的更多範例程式碼,請參閱 ApiDemos 中的 RemoteService.java 類別。

管理繫結服務的週期

服務與所有用戶端解除繫結時,Android 系統會將服務終結 (除非服務是和 onStartCommand() 一起啟動的)。 如果您的服務純粹是繫結服務,就不用管理它的週期— Android 系統會根據服務是否繫結至任何用戶端,為您管理服務。

不過,如果您選擇實作 onStartCommand() 回呼方法,則必須明確停止服務,因為服務現在會視為「已啟動」。 如果是此情形,除非服務本身使用 stopSelf() 自行停止,或另一個元件呼叫 stopService() 加以停止,否則服務會持續執行,不論它是否繫結至任何用戶端。

此外,如果您的服務已啟動並且接受繫結,當系統呼叫您的 onUnbind() 方法時,可以選擇傳回 true (如果您希望用戶端下次繫結至服務時,可以接收 onRebind() 呼叫,而不是接收 onBind() 的呼叫)。onRebind() 會傳回空值,但用戶端仍然會在其 onServiceConnected() 回呼中接收到 IBinder。以下「圖 1」說明這類週期的邏輯。

圖 1.服務的週期開始後,就允許繫結行為。

如需關於已啟動服務週期的詳細資訊,請參閱服務文件。

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)