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

來源:互聯網
上載者:User

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

本文首先概述了Android的處理序間通訊的Binder機制,然後結合一個AIDL的例子,對Binder機制進行瞭解析。

概述

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

        Binder作為一種處理序間通訊機制,負責提供遠程調用的功能(RPC),它的系統組件主要包括四種:Client, Server, ServiceManager, Binder Driver. 它們之間的關係如所示:

 

        我們可以看出,Client, Server, ServiceManager運行在系統的使用者態,而Binder Driver運行在核心態。為了完成Client端到Server端的通訊任務,使用者空間的需要操作Binder Driver提供的/dev/binder檔案來完成互動。那麼ServiceManager的工作是什麼呢?ServiceManager負責管理Server並向Client端提供一個Server的代理介面(proxy)。通過代理介面中定義的方法,Client端就可以使用Server端提供的服務了。整個過程如下:

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

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

 

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

AIDL小栗子

       首先,建立一個aidl檔案“ICalculateAIDL.aidl”,這個介面裡面定義了要對外提供的服務,我們這裡定義了計算加法和減法的函數:

package com.cqumonk.calculate.aidl;interface ICalculateAIDL {    int add(int a,int b);    int minus(int a,int b);}

       建立好後,rebuild一下我們的項目,可以產生同名的java檔案。這是一個介面,裡麵包含了一個名為Stub的靜態抽象類別,以及我們在aidl檔案中定義的加減法函數。裡面詳細代碼在後面分析,介面檔案結構如下:

        然後我們需要實現服務端方面的功能,建立一個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,包含四個按鈕:

        然後我們可以在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;        }    };}

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

        點擊加法和減法按鈕,都可以完成計算並傳回值。然後點擊解除綁定按鈕:

        我們並未發現有串連斷開的日誌列印,這時候,我們繼續點擊加減法按鈕,發現仍然可以完成計算功能,這又是為毛呢?我們看到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進程間的通訊機制進行了分析,如果有錯誤的地方,歡迎指正。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.