從AIDL開始談Android進程間Binder通訊機制

本文首先概述了Android的進程間通訊的Binder機制,而後結合一個AIDL的例子,對Binder機制進行了解析。java

概述

        咱們知道,在Android app中的衆多activity,service等組件能夠運行在同一進程中,也能夠運行在不一樣進程中。當組件運行在同一進程中進行通訊就顯得比較簡單,在以前的Android線程間通訊機制中已經講過了;而當它們運行在不一樣的進程中時,就須要使用咱們本文中所要介紹的Binder機制了。android

        Binder做爲一種進程間通訊機制,負責提供遠程調用的功能(RPC),它的系統組件主要包括四種:Client, Server, ServiceManager, Binder Driver. 它們之間的關係以下圖所示:app

 

        從圖中咱們能夠看出,Client, Server, ServiceManager運行在系統的用戶態,而Binder Driver運行在內核態。爲了完成Client端到Server端的通訊任務,用戶空間的須要操做Binder Driver提供的/dev/binder文件來完成交互。那麼ServiceManager的工做是什麼呢?ServiceManager負責管理Server並向Client端提供一個Server的代理接口(proxy)。經過代理接口中定義的方法,Client端就可使用Server端提供的服務了。整個過程以下:ide

    • Client端調用代理接口的方法,將Client的參數打包爲parcel對象發送給內核空間中BinderDriver;
    • Server端讀取到BinderDriver中的請求數據,將parcel對象解包並處理;
    • 處理好後,將處理結果打包返回給BinderDriver,再交給Client端。

       另外,Client端與Server端的調用過程是同步的,即在Server返回結果以前,Client端是阻塞的。調用過程以下所示:函數

 

        OK,下面咱們經過AIDL的一個例子來分析一下以上過程時如何進行的。ui

AIDL小栗子

       首先,建立一個aidl文件「ICalculateAIDL.aidl」,這個接口裏面定義了要對外提供的服務,咱們這裏定義了計算加法和減法的函數:this

package com.cqumonk.calculate.aidl;

interface ICalculateAIDL {
    int add(int a,int b);
    int minus(int a,int b);
}

       建立好後,rebuild一下咱們的項目,能夠生成同名的java文件。這是一個接口,裏面包含了一個名爲Stub的靜態抽象類,以及咱們在aidl文件中定義的加減法函數。裏面詳細代碼在後面分析,接口文件結構以下:spa

image

        而後咱們須要實現服務端方面的功能,建立一個service,咱們順手把生命週期中各方法都打印出來,並完成加法和減法函數的實現:線程

public class CalculateService extends Service {
    private static final String TAG="SERVER";
    public CalculateService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"OnCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return mBinder;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        Log.e(TAG,"onRebind");
        super.onRebind(intent);
    }

    private final ICalculateAIDL.Stub mBinder=new ICalculateAIDL.Stub(){
        @Override
        public int add(int a, int b) throws RemoteException {
            return a+b;
        }

        @Override
        public int minus(int a, int b) throws RemoteException {
            return a-b;
        }
    };
}

        在service中,咱們使用剛剛生成的ICalculateAIDL.Stub靜態抽象類建立了一個Binder對象,實現了其中有關計算的業務方法,並在onBind方法中返回它。另外咱們還須要在manifest文件中對該服務進行註冊:代理

        <service
            android:name=".CalculateService"
            android:enabled="true"
            android:exported="true" >
            <intent-filter>
                <action android:name="com.cqumonk.adil.calculate"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

        這時候,咱們的服務端工做就完成了。咱們開始編寫客戶端的代碼來調用這個服務。首先,咱們定義一個activity,包含四個按鈕:

image

        而後咱們能夠在activity中啓動以前定義好的service並調用它所提供的服務:

public class MainActivity extends Activity implements View.OnClickListener {

    Button mBind;
    Button mUnbind;
    Button mAdd;
    Button mMinus;
    TextView txt_res;
    private static final String TAG="CLIENT";
    private ICalculateAIDL mCalculateAIDL;
    private boolean binded=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBind= (Button) findViewById(R.id.btn_bind);
        mUnbind= (Button) findViewById(R.id.btn_unbind);
        mAdd= (Button) findViewById(R.id.btn_add);
        mMinus= (Button) findViewById(R.id.btn_minus);
        txt_res= (TextView) findViewById(R.id.txt_res);

        mBind.setOnClickListener(this);
        mUnbind.setOnClickListener(this);
        mAdd.setOnClickListener(this);
        mMinus.setOnClickListener(this);
    }

    @Override
    protected void onStop() {

        super.onStop();
        unbind();
    }

    private void unbind(){
        if (binded){
            unbindService(mConnection);
            binded=false;
        }
    }

    @Override
    public void onClick(View v) {
        int id=v.getId();
        switch (id){
            case R.id.btn_bind:
                Intent intent=new Intent();
                intent.setAction("com.cqumonk.adil.calculate");
                bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbind:
                unbind();
                break;
            case R.id.btn_add:
                if(mCalculateAIDL!=null){
                    try {
                        int res=mCalculateAIDL.add(3,3);
                        txt_res.setText(res+"");
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else{
                    Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_minus:
                if(mCalculateAIDL!=null){
                    try {
                        int res=mCalculateAIDL.minus(9,4);
                        txt_res.setText(res+"");
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else{
                    Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
    private ServiceConnection mConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"connect");
            binded=true;
            mCalculateAIDL=ICalculateAIDL.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG,"disconnect");
            mCalculateAIDL=null;
            binded=false;
        }
    };
}

        當咱們點擊綁定按鈕,觀察日誌:

image

        點擊加法和減法按鈕,均可以完成計算並返回值。而後點擊解綁按鈕:

image

        咱們並未發現有鏈接斷開的日誌打印,這時候,咱們繼續點擊加減法按鈕,發現仍然能夠完成計算功能,這又是爲毛呢?咱們看到unbind和destroy的日誌打印,說明鏈接已經斷開,service已經被銷燬,可是咱們返回的stub對象仍然是能夠繼續使用的。而並非說service仍然在運行。

過程分析

        首先,咱們調用bindservice方法來啓動了service:

        一方面鏈接成功時調用了serviceConnection的onServiceConnected方法。咱們從該方法中獲取到一個Binder對象(注意,這個binder並非咱們在service中實現的那個哦。當service時本apk中的service時,這裏返回的是同一個binder),咱們經過此binder來與server進行通訊。爲了區分,咱們稱爲clientBinder。

public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"connect");
            binded=true;
            mCalculateAIDL= ICalculateAIDL.Stub.asInterface(service);       
}

        另外一方面,onBind方法返回了咱們實現的Stub對象,其實也是一個Binder,用於和Client進行通訊。(以前咱們定義了一個aidl接口文件,並根據它生成了ICalculateAIDL接口。這個接口中有咱們定義的兩個方法以及一個靜態抽象內部類Stub,它是一個Binder的子類。)咱們來看一下Stub是如何定義的:

public static abstract class Stub extends android.os.Binder implements com.cqumonk.calculate.aidl.ICalculateAIDL

        它繼承了Binder類也實現了ICalculateAIDL接口,咱們實現了定義的add和minus方法。咱們仍然要強調一下它並非clientBinder,在負責與clientBinder進行通訊交互的同時,它也維護了service描述符與服務端service的映射。

 

        咱們在Client中的onServiceConnected裏調用了stub對象的asInterface方法,並將以前獲得的clientBinder傳入:

public static com.cqumonk.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//根據包名獲取本地實現的一個接口的實例,若是是本地service則能夠獲取到             if (((iin != null) && (iin instanceof com.cqumonk.calculate.aidl.ICalculateAIDL))) {
                return ((com.cqumonk.calculate.aidl.ICalculateAIDL) iin);  //若是獲得的實例是ICalculateAIDL的對象,則返回             }
            return new com.cqumonk.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);//若是沒法獲得本地實現的對象則會返回一個代理對象 }

        在這個方法中,首先在系統中查找註冊的的service,若是沒有找到,那麼必定是別的apk實現的service,因而返回一個此service的靜態代理類對象供Client調用。咱們來看一下這個代理,建立時咱們將clientBinder傳入了,同時也它實現了ICalculateAIDL接口:

private static class Proxy implements com.cqumonk.calculate.aidl.ICalculateAIDL {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);  //將參數打包                     _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);  //調用binderDriver的提供的方法將參數發給服務端                     _reply.readException();
                    _result = _reply.readInt();  //讀取到返回結果                 } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int minus(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_minus, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        代理中也實現了ICalculateAIDL接口定義的方法,咱們以add方法爲例,裏面將參數打包發送給Server端。在Server端收到請求後,會調用service中咱們實現的那個stub對象(mBinder)的onTransact方法:

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1); //調用咱們實現好的方法                     reply.writeNoException();
                    reply.writeInt(_result); //把結果返回                     return true;
                }
                case TRANSACTION_minus: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.minus(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        調用完成後把結果打包返回給Poxy處理,最後返回給客戶端。

總結

        由上面的例子咱們能夠看出,在跨進程通訊的時候,Client端使用的Poxy裏面封裝了一個binder與Server端的stub(也是一個binder對象)進行交互,兩個binder做爲接口調用BinderDriver的transact來發送數據包,以及onTransact接收處理數據包。

        經過結合AIDL例子,咱們對Android進程間的通訊機制進行了分析,若是有錯誤的地方,歡迎指正。

相關文章
相關標籤/搜索