Android 進程間通信——Service、Messenger


概述

介紹綁定服務端的三種方式:同一進程綁定服務、跨進程綁定服務(Messenger)、跨進程綁定服務(aidl)。 重點說一下通過Messenger、Service實現的進程間通信。

詳細

一、准備工作

 

開發環境:

jdk1.8

Eclipse Luna Service Release 1 (4.4.1)

運行環境:

華為榮耀6(Android4.4)、華為p9(Android7.0)

實現功能:

Messenger、Service實現的進程間通信

二、基礎知識

bound服務是客戶端 - 服務器結構中的服務器。 bound服務允許組件(如Activity)綁定到服務,發送請求,接收響應,甚至執行進程間通信(IPC)。 綁定的服務通常僅服務於另一個應用程序組件,並且不會無限期地在后台運行。

bound服務是Service類的一個實現,它允許其他應用程序綁定到它並與之交互。 要提供綁定服務,您必須實現onBind()回調方法。 此方法返回一個IBinder對象,該對象定義了客戶端與服務進行交互的編程接口。

客戶端可以通過調用bindService()綁定到該服務。當它這樣做時,它必須實現ServiceConnection,監視與服務的連接。 bindService()方法立即返回,沒有返回值,但是當Android系統創建客戶端和服務之間的連接時,它會回調ServiceConnection上的onServiceConnected()來傳遞給客戶端IBinder對象,客戶端可通過IBinder對象與服務通信。

多個客戶端可以全部連接到該服務。但是,系統只會在第一個客戶端綁定時調用您的服務的onBind()方法來檢索IBinder。系統然后將相同的IBinder傳遞給綁定的任何其他客戶端,而無需再次調用onBind()。

當最后一個客戶端解除綁定服務時,系統會銷毀該服務(除非該服務也由startService()啟動)。

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

三、擴展Binder類

在創建提供綁定的Service時,必須提供一個IBinder (客戶端可以用來與服務進行交互的編程接口)。有三種方法可以定義接口:

1、擴展Binder類

如果您的Service對您自己的應用程序是私有的,並且與客戶端在相同的進程中運行(這是常見的),則應該通過擴展Binder類並創建其實例,onBind()返回該實例。 客戶端收到Binder,可以使用它直接訪問Binder實現或甚至Service中可用的公共方法。

當您的服務只是您自己的應用程序的后台工作者時,這是首選技術。您不會以這種方式創建界面的唯一原因是因為您的服務被其他應用程序或單獨的進程使用。

2、使用Messenger

如果您需要Service 和客戶端位於不同的進程,則可以使用Messenger為服務創建一個interface。 以這種方式,服務定義響應不同類型的Message對象的Handler。 該Handler是Messenger的基礎,可以與客戶端共享IBinder,允許客戶端使用Message對象向服務發送命令。 此外,客戶端可以定義自己的Messenger,因此服務可以發回消息。

這是執行進程間通信(IPC)的最簡單的方法,因為Messenger將所有請求排隊到單個線程中,以便您不必將服務設計為線程安全。

3、使用AIDL

AIDL(Android Interface Definition Language)執行所有的工作,將對象分解為基元,操作系統可以在進程之間了解和編組它們以執行IPC。 之前使用的Messenger技術實際上是基於AIDL作為其底層結構。 如上所述,Messenger在單個線程中創建所有客戶端請求的隊列,因此服務一次接收一個請求。 但是,如果您希望您的服務同時處理多個請求,則可以直接使用AIDL。 在這種情況下,您的服務必須能夠進行多線程並建立線程安全。

要直接使用AIDL,您必須創建一個定義編程接口的.aidl文件。 Android SDK工具使用此文件生成一個實現接口並處理IPC的抽象類,然后您可以在服務中擴展它。

注意:大多數應用程序不應該使用AIDL創建綁定的服務,因為它可能需要多線程功能,並可能導致更復雜的實現。因此,AIDL不適用於大多數應用程序。

四、擴展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;
        }
    };
}

五、進程間通信(使用Messenger)

如果您需要服務與遠程進程通信,那么可以使用Messenger為服務提供接口。這種技術允許執行進程間通信(IPC),而無需使用AIDL。

以下是Messenger使用總結:

A、Service實現一個Handler,接收客戶端發送的消息。

B、Handler用於創建一個Messenger對象(這是對Handler的引用)。

C、Messenger創建一個IBinder,Service將IBinder 從onBind()返回給客戶端。

D、客戶端使用IBinder來實例化Messenger (引用服務的Handler),客戶端利用Messenger將Message對象發送到服務。

E、Service接收 Message在其Handler的handleMessage()方法中進行處理。

以這種方式,客戶端沒有“方法”可以調用該服務。相反,客戶端提供Service在其Handler中接收的“消息”(Message對象)。

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

image.png

image.png

 

服務端

服務端沒有Activity(沒有界面),主要是一個Service——RemoteService。

服務端沒有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對象

@Overridepublic IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind");    //通過Mesenger對象獲取IBinder實例,並返回
    return mMessenger.getBinder();
}

4 服務端接收客戶端發送的消息,並在Handler對象的hanleMessage方法中進行處理。

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()。然后,

應用程序組件(客戶端)可以通過調用綁定到服務 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發送消息(控制播放音樂、停止播放)。

六、管理綁定服務的生命周期

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

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

image.png

七、程序代碼截圖

1、客戶端

image.png

2、服務端

image.png

八、運行效果

運行,右鍵項目:Run as -》Android Application

image.png

image.png

 

注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM