5.2 綁定服務的三種方式:擴展 Binder 類、使用 Messenger、使用 AIDL

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

綁定服務指的是客戶端和服務器接口中的服務器,綁定服務可讓組件(例如 Activity)綁定到服務、發送請求、接收響應,甚至執行進程間通信 (IPC)。 綁定服務通常只在爲其他應用組件服務時處於活動狀態,不會無限期在後臺運行。本篇向大家介紹如何創建綁定服務,包括如何綁定到來自其他應用組件的服務。

一、綁定服務簡介

綁定服務是 Service 類的實現,可讓其他應用與其綁定和交互。要提供服務綁定,我們必須實現 onBind()回調方法。該方法返回的 IBinder 對象定義了客戶端用來與服務進行交互的編程接口。

綁定到已啓動服務可以創建同時具有已啓動和綁定兩種狀態的服務。 也就是說,我們可通過調用 startService()啓動該服務,讓服務無限期運行;此外,我們還可通過調用 bindService() 使客戶端綁定到服務。如果我們確實允許服務同時具有已啓動和綁定狀態,則服務啓動後,系統不會在所有客戶端都取消綁定時銷燬服務。 所以我們必須通過調用stopSelf() 或 stopService() 顯式停止服務。

儘管我們通常應該實現 onBind() 或 onStartCommand(),但有時需要同時實現這兩者。例如,音樂播放器可能發現讓其服務無限期運行並同時提供綁定很有用處。 這樣一來,Activity 便可啓動服務進行音樂播放,即使用戶離開應用,音樂播放也不會停止。 然後,當用戶返回應用時,Activity 可綁定到服務,重新獲得回放控制權。

客戶端可通過調用 bindService() 綁定到服務。我們調用時,它必須提供 ServiceConnection 的實現,後者會監控與服務的連接。bindService() 方法會立即無值返回,但當 Android 系統創建客戶端與服務之間的連接時,會對 ServiceConnection 調用 onServiceConnected(),向客戶端傳遞用來與服務通信的IBinder。多個客戶端可同時連接到一個服務。不過,只有在第一個客戶端綁定時,系統纔會調用服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次調用 onBind(),便可將同一 IBinder 傳遞至任何其他綁定的客戶端。

當最後一個客戶端取消與服務的綁定時,系統會將服務銷燬(除非 startService() 也啓動了該服務)。
當我們實現綁定服務時,最重要的環節是定義我們的 onBind() 回調方法返回的接口。我們可以通過幾種不同的方法定義服務的 IBinder 接口,接下來我們對這些方法逐一做講解。

二、創建綁定服務

創建提供綁定的服務時,我們必須提供 IBinder,用以提供客戶端與服務進行交互的編程接口。 我們可以通過三種方法定義接口:

1、擴展 Binder 類

如果服務是供我們的自有應用專用,並且在與客戶端在相同的進程中運行(常見情況),則應通過擴展 Binder 並從 onBind() 它的一個實例來創建接口。客戶端收到 Binder,可利用它直接訪問 Binder 實現中乃至 Service 中可用的公共方法。如果服務只是我們的自有應用的後臺工作線程,則優先採用這種方法。而當我們的服務需要被其他應用或不同的進程佔用時,我們則不以這種方式創建接口。

2、使用 Messenger

不同於以上的方法,如果我們需要讓接口跨不同的進程工作,則可使用 Messenger 爲服務創建接口。服務可以用這種方式定義對應於不同類型 Message 對象的 Handler。此 Handler 是 Messenger 的基礎,而 Messenger 可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 對象向服務發送命令。此外,客戶端還可定義自有的 Messenger,以便服務回傳消息。這是執行進程間通信 (IPC) 的最簡單方法,因爲 Messenger 會在單一線程中創建包含所有請求的隊列,這樣我們就不必對服務進行線程安全設計。

3、使用 AIDL

AIDL(Android 接口定義語言)執行所有將對象分解成原語的工作,操作系統可以識別這些原語並將它們編組到各進程中,以執行 IPC。 之前採用 Messenger 的方法實際上是以 AIDL 作爲其底層結構。Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。不過,如果我們想讓服務同時處理多個請求的話,則可直接使用 AIDL。 在此情況下,我們的服務必須具備多線程處理能力,並採用線程安全式設計。如需直接使用 AIDL,我們必須創建一個定義編程接口的 .aidl 文件。Android SDK 工具利用該文件生成一個實現接口並處理 IPC 的抽象類,我們隨後可在服務內對其進行擴展。

注意:大多數應用“都不會”使用 AIDL 來創建綁定服務,因爲它可能要求具備多線程處理能力,並可能導致實現的複雜性增加。因此,AIDL 並不適合大多數應用,所以我們本篇也不會過多解釋如何將其用於我們的服務,我們會在後續的篇幅當中做相關介紹。

三、擴展 Binder 類

如果我們的服務僅供本地應用使用,不需要跨進程工作,則可以實現自有 Binder 類,讓我們的客戶端通過該類直接訪問服務中的公共方法。

注意:此方法只有在客戶端和服務位於同一應用和進程內這一最常見的情況下方纔有效。 例如,對於需要將 Activity 綁定到在後臺播放音樂的自有服務的音樂應用,此方法非常有效。

以下是具體的設置方法:

1、在我們的服務中,創建一個可滿足下列任一要求的 Binder 實例:
  • 包含客戶端可調用的公共方法
  • 返回當前 Service 實例,其中包含客戶端可調用的公共方法
  • 或返回由服務承載的其他類的實例,其中包含客戶端可調用的公共方法
2、從 onBind() 回調方法返回此 Binder 實例。
3、在客戶端中,從 onServiceConnected() 回調方法中接收 Binder,並使用提供的方法調用綁定服務。

注意:之所以要求服務和客戶端必須在同一應用內,是爲了便於客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因爲此方法不執行任何跨進程編組。

以下這個服務可讓客戶端通過 Binder 實現訪問服務中的方法:
public class LocalService extends Service {
    // 提供給客戶端的 Binder
    private final IBinder mBinder = new LocalBinder();
    // 隨機數生產器
    private final Random mGenerator = new Random();

    public class LocalBinder extends Binder {
        LocalService getService() {
            // 返回它的實例,以便客戶端可以調用它的公共方法
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** 提供給客戶端的公共方法 */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

LocalBinder 爲客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端便可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber()。

點擊按鈕時,以下這個 Activity 會綁定到 LocalService 並調用 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();
        // 綁定到 LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 從 service 解綁
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    public void onButtonClick(View v) {
        if (mBound) {
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** 綁定服務的回調 */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

四、使用 Messenger

如需讓服務與遠程進程通信,則可使用 Messenger 爲我們的服務提供接口。利用此方法,我們無需使用 AIDL 便可執行進程間通信 (IPC)。

Messenger 的使用方法摘要:
  • 服務實現一個 Handler,由其接收來自客戶端的每個調用的回調
  • Handler 用於創建 Messenger 對象(對 Handler 的引用)
  • Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端
  • 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然後使用後者將 Message 對象發送給服務
  • 服務在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每個 Message。

這樣,客戶端並沒有調用服務的“方法”。而客戶端傳遞的“消息”(Message 對象)是服務在其 Handler 中接收的。

以下是一個使用 Messenger 接口的簡單服務示例:
public class MessengerService extends Service {
    static final int MSG_SAY_HELLO = 1;

    /**
     * 來自客戶端消息的 Handler.
     */
    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);
            }
        }
    }

    /**
     * 暴露給客戶端用來和IncomingHandler進行通信的Messenger 
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * 當綁定這個服務的時候,返回一個接口給客戶端。
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

值得我們注意的是,服務就是在 Handler 的 handleMessage() 方法中接收傳入的 Message,並根據 what 成員決定下一步操作的。而客戶端只需根據服務返回的 IBinder 創建一個 Messenger,然後利用 send() 發送一條消息。

綁定到服務並向服務傳遞 MSG_SAY_HELLO 消息的 Activity:

public class ActivityMessenger extends Activity {
    /** 用來和service 通信的Messenger */
    Messenger mService = null;

    /** 綁定服務標誌位 */
    boolean mBound;

    /**
     * 綁定服務的回調
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        // 創建併發送一個消息到這個service
        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();
        bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

五、管理綁定服務的生命週期

當服務與所有客戶端之間的綁定全部取消時,Android 系統便會銷燬服務(除非還使用 onStartCommand() 啓動了該服務)。因此,如果我們的服務是純粹的綁定服務,則無需對其生命週期進行管理,Android 系統會根據它是否綁定到任何客戶端代您管理。

不過,如果我們選擇實現 onStartCommand() 回調方法,則我們必須顯式停止服務,因爲系統現在已將服務視爲已啓動(即通過startService()方法啓動的)。在此情況下,服務將一直運行到其通過 stopSelf() 自行停止,或其他組件調用 stopService() 爲止,無論其是否綁定到任何客戶端。

而如果我們的服務已啓動並接受綁定,則當系統調用您的 onUnbind() 方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind() 調用,則可選擇返回 true。不過onRebind() 返回空值的話,客戶端仍可在其 onServiceConnected() 回調中接收 IBinder。下圖說明了該情況的邏輯流程:

點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:


發佈了81 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章