Android 程式間通訊 Service、Messenger

weixin_34006468發表於2017-07-20

轉載請註明出處:http://www.jianshu.com/p/e6867aa8f267

Android四大元件(二)Service
接著上一篇Service基礎知識,這一篇主要說下介紹下繫結的服務端的三種方式:同一程式繫結服務、跨程式繫結服務(Messenger)、跨程式繫結服務(aidl)。
重點說一下通過Messenger、Service實現的程式間通訊(demo下載地址見最下面)。


1 基礎

bound服務是客戶端 - 伺服器結構中的伺服器。 bound服務允許元件(如Activity)繫結到服務,傳送請求,接收響應,甚至執行程式間通訊(IPC)。 繫結的服務通常僅服務於另一個應用程式元件,並且不會無限期地在後臺執行。
bound服務是Service類的一個實現,它允許其他應用程式繫結到它並與之互動。 要提供繫結服務,您必須實現onBind()回撥方法。 此方法返回一個IBinder物件,該物件定義了客戶端與服務進行互動的程式設計介面。

客戶端可以通過呼叫bindService()繫結到該服務。當它這樣做時,它必須實現ServiceConnection,監視與服務的連線。 bindService()方法立即返回,沒有返回值,但是當Android系統建立客戶端和服務之間的連線時,它會回撥ServiceConnection上的onServiceConnected()來傳遞給客戶端IBinder物件,客戶端可通過IBinder物件與服務通訊。
多個客戶端可以全部連線到該服務。但是,系統只會在第一個客戶端繫結時呼叫您的服務的onBind()方法來檢索IBinder。系統然後將相同的IBinder傳遞給繫結的任何其他客戶端,而無需再次呼叫onBind()。
當最後一個客戶端解除繫結服務時,系統會銷燬該服務(除非該服務也由startService()啟動)。

實現繫結服務時,最重要的部分是定義onBind()回撥方法返回的介面。您可以通過幾種不同的方法來定義服務的IBinder介面,以下部分將討論每種技術。


2 繫結服務方式

在建立提供繫結的Service時,必須提供一個IBinder (客戶端可以用來與服務進行互動的程式設計介面)。有三種方法可以定義介面:

  • 擴充套件Binder類
    如果您的Service對您自己的應用程式是私有的,並且與客戶端在相同的程式中執行(這是常見的),則應該通過擴充套件Binder類並建立其例項,onBind()返回該例項。 客戶端收到Binder,可以使用它直接訪問Binder實現或甚至Service中可用的公共方法。
    當您的服務只是您自己的應用程式的後臺工作者時,這是首選技術。您不會以這種方式建立介面的唯一原因是因為您的服務被其他應用程式或單獨的程式使用。
  • 使用Messenger
    如果您需要Service 和客戶端位於不同的程式,則可以使用Messenger為服務建立一個interface。 以這種方式,服務定義響應不同型別的Message物件的Handler。 該Handler是Messenger的基礎,可以與客戶端共享IBinder,允許客戶端使用Message物件向服務傳送命令。 此外,客戶端可以定義自己的Messenger,因此服務可以發回訊息。
    這是執行程式間通訊(IPC)的最簡單的方法,因為Messenger將所有請求排隊到單個執行緒中,以便您不必將服務設計為執行緒安全。
  • 使用AIDL
    AIDL(Android Interface Definition Language)執行所有的工作,將物件分解為基元,作業系統可以在程式之間瞭解和編組它們以執行IPC。 之前使用的Messenger技術實際上是基於AIDL作為其底層結構。 如上所述,Messenger在單個執行緒中建立所有客戶端請求的佇列,因此服務一次接收一個請求。 但是,如果您希望您的服務同時處理多個請求,則可以直接使用AIDL。 在這種情況下,您的服務必須能夠進行多執行緒並建立執行緒安全。
    要直接使用AIDL,您必須建立一個定義程式設計介面的.aidl檔案。 Android SDK工具使用此檔案生成一個實現介面並處理IPC的抽象類,然後您可以在服務中擴充套件它。

注意:大多數應用程式不應該使用AIDL建立繫結的服務,因為它可能需要多執行緒功能,並可能導致更復雜的實現。因此,AIDL不適用於大多數應用程式。

3 擴充套件Binder類

如果您的服務僅由本地應用程式使用,並且不需要跨程式工作,那麼您可以實現自己的Binder類,為客戶端直接訪問服務中的公共方法。

注意:只有當客戶端和服務處於相同的應用程式和程式中時,這是最常見的。 例如,將Activity繫結到其自己的Service對於需要在後臺播放音樂的音樂應用程式將是有效的。

具體使用方法:

  1. 在您的服務中,建立一個或多個例項Binder:
    包含客戶端可以呼叫的公共方法
    返回當前Service例項,該例項具有客戶端可以呼叫的公共方法
    或者,使用客戶端可以呼叫的公共方法返回由服務託管的另一個類的例項
  2. Binder從onBind()回撥方法返回此例項。
  3. 在客戶端中,Binder從onServiceConnected()回撥方法接收並使用提供的方法呼叫繫結的服務。

以下是一種通過Binder實現為客戶端訪問服務中的方法的服務:

public class LocalService extends Service {
    // 建立IBinder物件
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * 用於客戶端繫結的類。 因為我們知道這個服務總是和客戶端一樣執行在統一程式,
     * 所以我們不需要處理IPC
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            //返回此LocalService例項,以便客戶端可以呼叫公共方法
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回LocalBinder例項
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

LocalBinder類繼承了Binder,並提供了getService()方法,該方法返回LocalService例項。
LocalServcie的onBind方法返回LocalBinder例項。
客戶端獲取該LocalBinder例項,然後通過呼叫getService()方法獲取LocalServcie。這允許客戶端呼叫該Service中的公共方法。例如,客戶端可以從服務呼叫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();
        // 繫結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;
        }
    }

    //單擊按鈕時呼叫。
    public void onButtonClick(View v) {
        if (mBound) {
            // 呼叫LocalService的一個方法。
            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) {
            // 繫結到LocalService,轉換IBinder,獲取LocalService例項。
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

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

4 程式間通訊(使用Messenger)

如果您需要服務與遠端程式通訊,那麼可以使用Messenger為服務提供介面。這種技術允許執行程式間通訊(IPC),而無需使用AIDL。

以下是Messenger使用總結:

  • Service實現一個Handler,接收客戶端傳送的訊息。
  • Handler用於建立一個Messenger物件(這是對Handler的引用)。
  • Messenger建立一個IBinder,Service將IBinder 從onBind()返回給客戶端。
  • 客戶端使用IBinder來例項化Messenger (引用服務的Handler),客戶端利用Messenger將Message物件傳送到服務。
  • Service接收 Message在其Handler的handleMessage()方法中進行處理。

以這種方式,客戶端沒有“方法”可以呼叫該服務。相反,客戶端提供Service在其Handler中接收的“訊息”(Message物件)。

如下是一個使用Messenger實現程式間通訊的例項。由兩個應用程式,一個是服務端工程——RemoteService,另一個是客戶端工程——LocalClient。


6907877-8601cc2846b2b0b1
服務建立
6907877-aa497b0dee133bc8
客戶端操作服務端

服務端

服務端沒有Activity(沒有介面),主要是一個Service——RemoteService。
1 service建立Handler物件。

/**
 * 處理來自客戶端的訊息
 */
//建立Handler物件
private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        Log.i(TAG, "msg.what="+msg.what);
        Log.i(TAG, "mHandler Thread ="+Thread.currentThread());
        switch (msg.what) {
            case MSG_SAY_HELLO:
                Toast.makeText(getApplicationContext(), "hello,remote service", Toast.LENGTH_SHORT).show();
                //通過message物件獲取客戶端傳遞過來的Messenger物件。
                Messenger messenger = msg.replyTo;
                if(messenger != null){
              Message messg = Message.obtain(null, MSG_SAY_HELLO);
              try {
                //向客戶端傳送訊息
            messenger.send(messg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
                }
                break;
            case MSG_MUSIC_PLAY:
                //播放音樂
                startPlay();
                break;
            case MSG_MUSIC_STOP:
                //停止播放
                stopPlay();
                break;
            default:
                break;
        }
    }
};

2 建立Messenger物件

//建立Mesenger 物件
Messenger mMessenger = new Messenger(mHandler);

3 onBind()返回IBinder物件

@Override
public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind");
    //通過Mesenger物件獲取IBinder例項,並返回
    return mMessenger.getBinder();
}

4 服務端接收客戶端傳送的訊息,並在Handler物件的hanleMessage方法中進行處理。
Handler接收客戶端發來的Message訊息,並進行處理。可以通過msg.replyTo獲取客戶端傳遞的Messenger物件(前提是客戶端設定了該物件),通過該Messenger物件可以實現服務端向客戶端傳送訊息。
播放音樂和停止播放的程式碼就不貼了。

清單檔案AndroidManifest.xml

<service
    android:name="com.zpengyong.service.RemoteService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.zpengyong.messanger"></action>
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

exported要設定為true,否則別的程式不能使用該Service。

客戶端

應用程式元件(客戶端)可以通過呼叫繫結到服務 bindService()。然後,Android系統呼叫該Service的onBind()方法,該方法返回一個IBinder用於與服務進行互動的方法。

繫結是非同步的。bindService()立即返回,並不會返回IBinder給客戶端。要接收IBinder,客戶端必須建立一個例項ServiceConnection並將其傳遞給bindService()。在ServiceConnection包括系統呼叫提供的回撥方法IBinder。

注意:只有活動,服務和內容提供商可以繫結到服務 - 您無法從廣播接收方繫結到服務。

1 繫結Service
其中intent的action和Service註冊的action保持一致。

private void bind() {
    mBindState = STATE_CONNECTING;
    Intent intent = new Intent();
    // 設定action 與service在清單檔案中的action保持一致
    intent.setAction("com.zpengyong.messanger");
    // 繫結服務
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    mStateText.setText("connecting");
}

2 連線成功回撥
實現ServiceConnection,必須覆蓋兩個回撥方法:

  1. onServiceConnected()
    系統呼叫此方法來傳遞IBinder由服務onBind()方法返回的結果。
  2. onServiceDisconnected()
    當與服務的連線意外丟失時,例如服務已崩潰或已被殺死時,Android系統會呼叫此操作。當客戶端解除繫結(unbindService)時,這不被呼叫。

客戶端與遠端Servcie連線成功,系統�會回撥onServiceConnected()。通過IBinder引數獲取Messenger,該Messenger用來向Service傳送訊息。

private ServiceConnection mConnection = new ServiceConnection() {

    // 當與服務端連線成功時,回撥該方法。
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected");
        //通過IBinder引數獲取Messenger
        mRemoteMessenger = new Messenger(service);
        mStateText.setText("connected");
        mBindState = STATE_CONNECTED;
        mBtnBind.setText("解綁");
    }

    // 當與服務端連線異常斷開時,回撥該方法。
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected");
        mRemoteMessenger = null;
        mStateText.setText("disconnected");
        mBindState = STATE_DISCONNECTED;
        mBtnBind.setText("繫結");
    }
};

3 向服務端傳送訊息
Message屬性replyTo不是必須的,如果希望Service向客戶端傳送訊息則需要。

private void sendMsg(int what){
    if (mRemoteMessenger == null)
        return;
    //建立訊息,設定what屬性
    Message msg = Message.obtain(null, what);
    //replyTo不是必須的,如果希望Service向客戶端傳送訊息則需要設定。
    msg.replyTo = mMessenger;
    try {
        //傳送訊息
        mRemoteMessenger.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

sendMsg(MSG_SAY_HELLO); 向服務端打招呼。
sendMsg(MSG_MUSIC_PLAY); 向服務端傳送訊息 播放音樂
sendMsg(MSG_MUSIC_STOP); 向服務端傳送訊息 停止播放

4 接收Service的回覆
Handler中接收到Service傳送到訊息,進行處理。

private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        case MSG_SAY_HELLO:
            Log.i(TAG,"MSG_SAY_HELLO");
            break;
        default:
            break;
        }
    };
};
//向Service傳送訊息所用
private Messenger mRemoteMessenger = null;
//接收Service的回覆所用
private Messenger mMessenger = new Messenger(mHandler);

5 解除繫結
需要斷開與服務的連線時,請呼叫unbindService()。

private void unbind() {
    mBindState = STATE_DISCONNECTING;
    //解除與Service的連線
    unbindService(mConnection);
    mStateText.setText("disconnecting");
    mBindState = STATE_DISCONNECTED;
    mStateText.setText("disconnected");
    mBtnBind.setText("繫結");
}

unbindService與Service斷開連線之後,由於我RemoteService是通過client的繫結開啟的,所以解除繫結後,RemoteService 會走onUnbind --》onDestroy。但是還有遠端Messenger物件,依然可以向向Service傳送訊息(控制播放音樂、停止播放)。


5 管理繫結服務的生命週期

當一個Service從所有客戶端解除繫結時,Android系統會銷燬它(除非它是通過startServiceon建立的)。因此,您無需管理服務的生命週期,如果它完全是一個有限的服務,Android系統將根據是否繫結到任何客戶端來管理您的服務。

另外,如果您的Service啟動並接受繫結,那麼當系統呼叫您的onUnbind()方法時,如果希望在下次客戶端繫結到服務時接收呼叫onRebind()(而不是呼叫onBind()),則可以選擇返回 true。但客戶端仍然在onServiceConnected()回撥中收到IBinder。下圖說明了這種生命週期的邏輯。


6907877-de5f8e9d96f74eb8
繫結服務生命週期

程式間通訊demo下載地址:http://www.demodashi.com/demo/10611.html

歡迎大家關注、評論、點贊
你們的支援是我堅持的動力。

6907877-40461f37e48a4f61
歡迎關注我的微信公眾號

相關文章