Android跨進程通信Binder、Messenger、AIDL彙總

 前言

提到的進程間通信(IPC:Inter-Process Communication),在Android系統中,一個進程是不能直接訪問另一個進程的內存的,需要提供一些機制在不同的進程之間進行通信,Android官方推出了AIDL(Android Interface Definition Language),它是基於Binder機制的,具體Binder機制的東西就很多了,網上很多資料,我們就不在這裏說了,推薦文章

跨進程通信方式

  • 實現IBinder
  • Messenger
  • AIDL

後面兩種可以跨進程通信,是基於Binder機制的通信方式。第一種我們多用於service直接的通信,但是當sevice被設爲遠程服務時(設爲:remote),我們就要用後面兩種方式來進行通信了。

概念

  • IPC:Inter-Process Communication,進程間的通信或跨進程通信。簡單點理解,一個應用可以存在多個進程,但需要數據交換就必須用IPC;或者是二個應用之間的數據交換。
  • Binder:Binder是Android的一個類,它實現了IBinder接口。從IPC角度來說,Binder是Android中的一種跨進程通信方式。通過這個Binder對象,客戶端就可以獲取服務端提供的服務或數據,這裏的服務包括普通服務和基於AIDL的服務。
  • AIDL:Android Interface Definition language,它是一種Android內部進程通信接口的描述語言。

區別

  •  Messenger是對AIDL的一種封裝,開發的時候不用再寫.aidl文件。結合我自身的使用,因爲不用去寫.aidl文件,相比起來,Messenger使用起來十分簡單。但前面也說了,Messenger本質上也是AIDL,故在底層進程間通信這一塊,兩者的效率應該是一樣的。
  • service端,Messenger處理client端的請求是單線程的,而AIDL是多線程的。使用AIDL的時候,service端每收到一個client端的請求時,就會啓動一個線程去執行相應的操作。而Messenger,service收到的請求是放在Handler的MessageQueue裏面,Handler大家都用過,它需要綁定一個Thread,然後通過Looper不斷循環取出message執行相關操作,這個過程是同步執行的。但是你也可以不直接處理請求,而是開啓線程或者使用線程池,這樣也可以實現異步。
  •  client端,使用AIDL獲取返回值是同步的,而Messenger是異步的。Messenger只提供了一個方法進行進程間通信,就是send(Message msg)方法,發送的是一個Message,沒有返回值,要拿到返回值,需要把client的Messenger作爲msg.replyTo參數傳遞過去,service端處理完之後,在調用客戶端的Messenger的send(Message msg)方法把返回值傳遞迴client,這個過程是異步的,而AIDL你可以自己指定方法,指定返回值,它獲取返回值是同步的。

使用場景

在確定使用什麼機制之前,首先了解應用場景。Android系統中,如果組件與服務通信是在同一進程,就使用第一種方式;如果是跨進程通信,使用第二種和第三種,兩者不同在於,Messenger不能處理多線程併發請求。

代碼實現 

1、實現IBinder

  對用於service中,一般使用bindService()啓動服務。這是一種比startService更復雜的啓動方式,同時使用這種方式啓動的service也能完成更多的事情,比如其他組件可向其發送請求,接受來自它的響應,甚至通過它來進行IPC等等。我們通常將綁定它的組件稱爲客戶端,而稱它爲服務端。 如果要創建一個支持綁定的service,我們必須要重寫它的onBind()方法。這個方法會返回一個IBinder對象,它是客戶端用來和服務器進行交互的接口。而要得到IBinder接口,我們通常有三種方式:繼承Binder類,使用Messenger類,使用AIDL。

服務端配置

public class BinderService extends Service {


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends Binder{

        public void setMessage(String s,BinderActivity.CallBack callBack){
            Log.i("Binder_Service", s );
            callBack.sendMsg("從服務端發來的Binder響應");
        }
    }

}

客戶端配置

public class BinderActivity extends AppCompatActivity {
    private BinderService.MyBinder mMyBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        setTitle("客戶端");
        //綁定服務
        Intent intent = new Intent(BinderActivity.this,BinderService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyBinder.setMessage("從客服端發來的Binder請求",new CallBack() {
                    @Override
                    public void sendMsg(String s) {
                        Log.i("Binder_Client", s );
                    }
                });
            }
        });
    }
    /**
     * 服務回調方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMyBinder = (BinderService.MyBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMyBinder = null;
        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁服務,回收資源
        unbindService(conn);
    }

   public interface CallBack{
        void sendMsg(String s);
    }
}
manifest註冊
 <service
            android:name="com.veer.aidl.BinderService">
        <!--android:process=":remote"-->
        </service>

注意不要使用遠程服務,如果一定要使用遠程服務就要用後面的兩種跨進程方式。

結果

服務端打印日誌:

08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Service: 從客服端發來的Binder請求

客戶端打印日誌:

08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Client: 從服務端發來的Binder響應

Messenger

以前講到跨進程通信,我們總是第一時間想到AIDL(Android接口定義語言),實際上,使用Messenger在很多情況下是比使用AIDL簡單得多的,兩種比較就能看出來。

大家看到Messenger可能會很輕易的聯想到Message,然後很自然的進一步聯想到Handler——沒錯,Messenger的核心其實就是Message以及Handler來進行線程間的通信。下面講一下通過這種方式實現IPC的步驟:

  • 服務端實現一個Handler,由其接受來自客戶端的每個調用的回調
  • 使用實現的Handler創建Messenger對象
  • 通過Messenger得到一個IBinder對象,並將其通過onBind()返回給客戶端
  • 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然後使用後者將 Message 對象發送給服務

服務端在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每個 Message

服務端代碼

public class MessengerService extends Service {

    private Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
    private class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0:
                    Log.i("Binder_Service", "從客戶端發來的Messenger請求");
                    Message message = Message.obtain(null,1);
                    try {
                        msg.replyTo.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }

        }
    }

}

客戶端代碼

public class MessengerActivity extends AppCompatActivity {
    private Messenger mMessenger;
    private Messenger mMyClientMessenger = new Messenger(new MyClientHandler());

    @SuppressLint("WrongConstant")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        setTitle("客服端");
        Intent intent = new Intent();
        intent.setAction("com.veer.aidl.MessengerService");
        intent.setPackage("com.veer.aidl");
        bindService(intent, mCoon, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    Message message = Message.obtain(null,0);
                    message.replyTo = mMyClientMessenger;
                    mMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });


    }

    private  class  MyClientHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.i("Binder_client", "從服務端發來的Messenger請求,已經接受到通知");
                    break;
                default:

                    super.handleMessage(msg);
            }

        }
    }
    /**
     * 服務回調方法
     */
    private ServiceConnection mCoon = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁服務,回收資源
        unbindService(mCoon);
    }

}
manifest註冊
 <service
            android:exported="true"
            android:enabled="true"
            android:name="com.veer.aidl.MessengerService">
            <intent-filter>
                <action android:name="com.veer.aidl.MessengerService"/>
            </intent-filter>
        </service>

運行結果

服務端

客戶端

用Messenger來進行IPC的話整體的流程是非常清晰的,Message在其中起到了一個信使的作用,通過它客戶端與服務端的信息得以互通。

AIDL

AIDL,即Android Interface Definition Language,Android接口定義語言。它是一種IDL語言,可以拿來生成用於IPC的代碼。在我看來,它其實就是一個模板。爲什麼這樣說呢?在我們的使用中,實際上起作用的並不是我們寫的AIDL代碼,而是系統根據它生成的一個IInterface實例的代碼。而如果大家多生成幾個這樣的實例,然後把它們拿來比較,你會發現它們都是有套路的——都是一樣的流程,一樣的結構,只是根據具體的AIDL文件的不同有細微的變動。所以其實AIDL就是爲了避免我們一遍遍的寫一些千篇一律的代碼而出現的一個模板。

那麼如何使用AIDL來通過bindService()進行線程間通信呢?基本上有下面這些步驟:

  • 服務端創建一個AIDL文件,將暴露給客戶端的接口在裏面聲明
  • 在service中實現這些接口
  • 客戶端綁定服務端,並將onServiceConnected()得到的IBinder轉爲AIDL生成的IInterface實例

通過得到的實例調用其暴露的方法

1、服務端

創建aidl接口

在面下面創建aidl文件夾,然後創建我們的aidl接口文件,創建完了、之後一定要重新編譯一下項目,因爲Androidstudio會自動幫我創建一下文件。

這裏是binder機制應用層的原理,感興趣的同學可以瞭解下。

然後開始寫服務端代碼

public class AidlService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int add(int num1, int num2) throws RemoteException {
            Log.i("Aidl_Service", "從客戶端發來的AIDL請求:num1->" + num1 + "::num2->" + num2);
            return num1+num2;
        }
    };
}

 

這裏也比較簡單,和service中binder類似,但不是集成binder  ,我們定義的aidl接口。

服務端manifest註冊

<service
            android:exported="true"
            android:enabled="true"
            android:name="com.veer.aidl.AidlService">
            <intent-filter>
                <action android:name="com.veer.aidl.AidlService"/>
            </intent-filter>
        </service>

客戶端配置

客戶端需要把服務端的aidl文件copy到客服端上來,放置的位置同服務端。然後一樣,重新編譯項目。

客戶端代碼

public class AidlActivity extends AppCompatActivity {

    IMyAidlInterface iMyAidlInterface;
    private TextView mTvService;

    @SuppressLint("WrongConstant")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        setTitle("客戶端");
        mTvService = (TextView) findViewById(R.id.tv_service);
        //綁定服務
        Intent intent = new Intent();
        intent.setAction("com.veer.aidl.AidlService");
        intent.setPackage("com.veer.aidl");
        bindService(intent, conn, BIND_AUTO_CREATE);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                add();
            }
        });

    }
    /**
     * 點擊“AIDL”按鈕事件
     */
    public void add() {
        try {
            if(iMyAidlInterface!=null){
                int res = iMyAidlInterface.add(1, 2);
                mTvService.setText("從服務端調用成功的結果:" + res);
                Log.i("Aidl_Client", "從服務端調用成功的結果:" + res);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務回調方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iMyAidlInterface = null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁服務,回收資源
        unbindService(conn);
    }

}

這裏和binder方式,獲取ibinder的方式不同。

服務端日誌

客戶端日誌

這樣我們就完成了兩個應用間的通信了。

Messenger與AIDL的比較

首先,在實現的難度上,肯定是Messenger要簡單的多——至少不需要寫AIDL文件了(雖然如果認真的究其本質,會發現它的底層實現還是AIDL)。另外,使用Messenger還有一個顯著的好處是它會把所有的請求排入隊列,因此你幾乎可以不用擔心多線程可能會帶來的問題。

但是這樣說來,難道AIDL進行IPC就一無是處了麼?當然不是,如果是那樣的話它早就被淘汰了。一方面是如果項目中有併發處理問題的需求,或者會有大量的併發請求,這個時候Messenger就不適用了——它的特性讓它只能串行的解決請求。另外,我們在使用Messenger的時候只能通過Message來傳遞信息實現交互,但是在有些時候也許我們需要直接跨進程調用服務端的方法,這個時候又怎麼辦呢?只能使用AIDL。

所以,這兩種IPC方式各有各的優點和缺點,具體使用哪種就看具體的需要了——當然,能使用簡單的就儘量使用簡單的吧。

總結

Android跨進程通信的知識點其實很多,我們這裏只是簡單介紹了用法,還需要我們更加深入的瞭解。我也一直會更新博客,項目我會放到GitHub上,一直維護,增加更加的功能。GitHub地址:https://github.com/fuweiwei/VAidl

源碼下載

源碼下載地址

GitHub地址

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章