AIDL使用及原理淺析

文章首發於我的博客java

參考:android

前言

對於AIDL, 我不是很熟悉, 由於在工做中沒有用到過.可是AIDL確實是Android跨進程通訊中最多見的方式, 因此學習一下是十分有必要的.git

AIDL簡介

AIDL (Android Interface Definition Language) 是一種接口定義語言,用於生成能夠在 Android 設備上兩個進程之間進行進程間通訊 (interprocess communication, IPC) 的代碼。若是在一個進程中(例如 Activity)要調用另外一個進程中(例如 Service)對象的操做,就可使用 AIDL 生成可序列化的參數,來完成進程間通訊。 簡言之,AIDL 可以實現進程間通訊,其內部是經過 Binder 機制來實現的。github

AIDL的使用

AIDL文件

首先創建一個AIDL文件IBookManager.aidl 這個文件咱們先不去管, 待會兒再來修改. 定義一個Book類, 實現Parcelable接口.網絡

public class Book implements android.os.Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    public Book() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.bookId);
        dest.writeString(this.bookName);
    }

    protected Book(Parcel in) {
        this.bookId = in.readInt();
        this.bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}
複製代碼

AIDL支持的數據類型:併發

  • 基本數據類型
  • String, CharSequence
  • Parcelable
  • ArrayList
  • HashMap

雖然可使用 ArrayList 與 HashMap, 可是其類型也只能用被AIDL支持的數據類型. 自定義的Parcelable對象和AIDL對象必需要顯式的import進來
因此最終IBookManager的實現:ide

// IBookManager.aidl
package me.mundane.testaidl.aidl;

// Declare any non-default types here with import statements
// 顯式的導入
import me.mundane.testaidl.aidl.Book;

interface IBookManager {
    /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);

    void addBook(in Book book);

    List<Book> getBookList();
}
複製代碼

另外, 若是AIDL文件中用到了自定義的Parcelable對象, 必須新建一個和它同名的AIDL文件, 並在其中聲明它爲Parcelable類型高併發

// IBookManager.aidl
package me.mundane.testaidl.aidl;

// Declare any non-default types here with import statements
parcelable Book;

複製代碼

除此以外, AIDL中除了基本數據類型, 其餘類型的參數必須標上方向: in表示輸入型參數, out表示輸出型參數, inout表示輸入輸出型參數 爲了方便AIDL的開發, 建議把全部和AIDL相關的類和文件所有放入一個包中, 方便整個複製
看一下個人分包結構 如今咱們Make Project, Android Studdio自動爲咱們生成Binder類 該類中有個重要的內部類Stub, 繼承了Binder類, 並實現了IBookManager接口 post

服務端

爲了實現進程間的通訊, 咱們須要建立一個獨立進程的service, 來模擬進程間的通訊, service充當服務端 ":remote"表示在當前的進程名前面附加上當前的包名, 而且是當前應用的私有進程學習

<service android:process=":remote" android:name=".aidl.BookManagerService" >
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>
                <action android:name="me.mundane.testaidl.aidl.BookManagerService"/>
            </intent-filter>
        </service>
複製代碼

在service中定義一個我上面說的那個重要的內部類IBookManager.Stub的對象, 並定義具體實現

private IBookManager.Stub mBinder = new Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
            final int bookCount = mBookList.size();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(BookManagerService.this,
                            String.format("添加了一本新書, 如今有%d本", bookCount), Toast.LENGTH_SHORT).show();
                }
            });
            Log.d(TAG, String.format("添加了一本新書, 如今有%d本", bookCount));
            Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }
    };
複製代碼

當Activity調用bindService()方法時, 會回調Service中的onBind()方法, 在onBind()方法中將上面定義的mBinder對象返回

@Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        return mBinder;
    }
複製代碼

這樣Activity就能獲得Stub實現對象, 這個對象是個Binder對象, 能夠實現進程間通訊.

客戶端

Activity充當服務端, 首先須要綁定service

public void bindService(View view) {
        Intent intent = new Intent();
        intent.setAction("me.mundane.testaidl.aidl.BookManagerService");
        intent.setPackage(getPackageName());
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }
複製代碼

注意這個mServiceConnection是咱們自定義的

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBookManager = IBookManager.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "綁定成功", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIBookManager = null;
        }
    };
複製代碼

能夠看到在onServiceConnected()方法裏返回了咱們獲得了一個IBookManager對象, 這其實就是service.onBind()方法中返回的Binder對象, 利用這個Binder對象咱們既能夠隨心所欲了, 好比addBook()

public void addBook(View view) {
        if (mIBookManager != null) {
            try {
                mIBookManager.addBook(new Book(18, "漫畫書"));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
複製代碼

效果圖

並且能夠看到, 確實起了兩個進程, 實現的是進程間通訊

兩個應用之間的通訊

下面來演示一下真正的兩個應用之間的通訊

Server

首先新建一個aidlserver的module 建立AIDL文件, 定義一個login()方法

// IMyAidlInterface.aidl
package me.mundane.aidlserver;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);

    void login(String userName, String password);
}

複製代碼

Make Module, 和上面同樣, Android Studio會自動生成一個IMyAidlInterface的.java文件 新建一個Service, 在onBind()的時候返回一個繼承了IMyAidlInterface.Stub的binder對象

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

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

    public interface OnLoginListener {
        void login(String username, String password);
    }

    private OnLoginListener mOnLoginListener;


    public void setOnLoginListener(OnLoginListener listener) {
        mOnLoginListener = listener;
        Log.d(TAG, "mOnLoginListener = " + mOnLoginListener);
    }

    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 void login(String userName, String password) throws RemoteException {
            Log.d(TAG, "mOnLoginListener = " + mOnLoginListener);
            if (mOnLoginListener != null) {
                mOnLoginListener.login(userName, password);
            }
        }

        public AIDLService getService() {
            return AIDLService.this;
        }


    }
}
複製代碼

在AndroidManifest爲Service設置默認的category和action, 使得可以經過隱式啓動來啓動這個Service

<service android:name=".AIDLService" >
            <intent-filter>
                <action android:name="me.mundane.aidlserver"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
複製代碼

再看一下咱們在MainActivity中的邏輯

public class MainActivity extends AppCompatActivity implements OnLoginListener {

    private TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = findViewById(R.id.tv);
        Intent intent = new Intent(this, AIDLService.class);
        bindService(intent, mAIDLConnection, Service.BIND_AUTO_CREATE);
    }

    private ServiceConnection mAIDLConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyBinder binder = (MyBinder) service;
            AIDLService aidlService = binder.getService();
            aidlService.setOnLoginListener(MainActivity.this);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private Handler mHandler = new Handler();

    @Override
    public void login(final String username, final String password) {

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mTv.setText(username + ", " + password);
                Toast.makeText(MainActivity.this, "登陸成功", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
複製代碼

Client

接在再建一個Client的module 把服務端 aidl 整個文件夾拷貝到客戶端 src/main 目錄下, 一樣的, Make Project一下. 接着再完善一下MainActivity中邏輯, 首先經過bindService(), 在onServiceConnected()的時候獲取一個binder對象, 在點擊登陸按鈕的時候將用戶名和密碼傳遞過去.

public class MainActivity extends AppCompatActivity {

    private IMyAidlInterface mIMyAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        // 服務端AndroidManifest.xml文件該Service所配置的action
        intent.setAction("me.mundane.aidlserver");
        // Service所在的包名
        intent.setPackage("me.mundane.aidlserver");
        bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);
    }

    public void login(View view) {
        if (mIMyAidlInterface != null) {
            try {
                mIMyAidlInterface.login("mundane", "123456");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    class ConnectCallBack implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

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

}
複製代碼

看一下效果, 能夠看到在Client端傳遞了用戶名和密碼, 在Server端獲取了用戶名和密碼, 實現了兩個應用之間的通訊, 很顯然是真正的跨進程通訊

AIDL的原理淺析

下面以第一個Book的例子淺析一下AIDL的原理 首先看服務端的這段代碼

@Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        return mBinder;
    }

    private IBookManager.Stub mBinder = new Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
            final int bookCount = mBookList.size();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(BookManagerService.this,
                            String.format("添加了一本新書, 如今有%d本", bookCount), Toast.LENGTH_SHORT).show();
                }
            });
            Log.d(TAG, String.format("添加了一本新書, 如今有%d本", bookCount));
            Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }
    };
複製代碼

咱們從源碼解析的角度, 看一下這個Stub的構造方法

public static abstract class Stub extends android.os.Binder implements IBookManager {
        private static final String DESCRIPTOR = "me.mundane.testaidl.aidl.IBookManager";

        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        ...
複製代碼

能夠看到這個內部類Stub是一個抽象類, 跟蹤this.attachInterface(this, DESCRIPTOR);

public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
複製代碼

能夠看到只是將它本身自己和DESCRIPTOR這個字符串標識做爲Binder中的全局變量保存了起來.DESRIPTOR做爲Binder 的惟一標識,通常用當前 Binder 的全類名錶示。

而後咱們看客戶端的這段代碼

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBookManager = IBookManager.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "綁定成功", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIBookManager = null;
        }
    };
複製代碼

跟蹤IBookManager.Stub.asInterface(service);這個方法

/** * Cast an IBinder object into an me.mundane.testaidl.aidl.IBookManager interface, * generating a proxy if needed. */
        public static IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IBookManager))) {
                return ((IBookManager) iin);
            }
            return new Proxy(obj);
        }
複製代碼

註釋是說將IBinder接口對象強轉爲IBookManager對象, 若是須要的話, 生成一個代理. 首先會根據標識符去 IBinder 的本地去查找是否有該對象,也就是調用 obj.queryLocalInterface(DESCRIPTOR) 方法,繼續源碼中 Binder.java

public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
複製代碼

意思就是若是本地存在這個標識符的 IInterface 對象,那就直接返回以前構造方法中初始化的 mOwner 對象,不然返回 null.由於咱們這裏涉及到了跨進程通訊,雖然服務端在初始化mBinder進行了attachInterface(this, DESCRIPTOR)(能夠再看看上面的源碼), 但那時另外一個進程的, 因此這裏會直接返回 null。接着就return一個 new Proxy(obj);, 咱們繼續跟蹤這個Proxy類

private static class Proxy implements IBookManager {
            private android.os.IBinder mRemote;

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

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

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addBook(Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.util.List<Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }
複製代碼

在客戶端中, 咱們調用的是IBookManager的addBook()方法, 也就是這個Proxy對象的addBook()方法, 觀察這個addBook()方法. 這裏就涉及到了一個重要的類 Parcel,Parcel 天生具有跨進程傳輸數據能力. 把須要傳遞的數據寫入 Parcel 中,而後到達目標進程後,將 Parcel 中的數據讀出便可,因此能夠將 Parcel 稱爲數據傳輸載體。 這裏的_data就是一個Parcel對象, 咱們將Book寫入Parcel, 而後調用mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);

這個mRemote其實就是以前傳進來的也就是Service返回的一個IBookManager.Stub對象, 咱們仍是再跟蹤一下這個transact()方法吧, 在Binder類中

/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
複製代碼

能夠看到onTransact()方法會被調用, 這裏實際上是這樣 client 端:BpBinder.transact() 來發送事務請求; server 端:BBinder.onTransact() 會接收到相應事務。 因此服務端的onTransact()方法會被調用, 其實就是IBookManager.Stub.onTransact()會被調用

@Override
        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_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
複製代碼

case TRANSACTION_addBook的時候, 調用addBook()方法, 這樣天然就完成了服務端中收到響應以後的操做.返回true表示這個事務是成功的. 當 onTransact 返回 true,調用成功,從 reply 對象中取出返回值,返回給客戶端調用方。

總結

高度歸納 AIDL 的用法,就是服務端裏有一個 Service,給與之綁定 (bindService) 的特定客戶端進程提供 Binder 對象。客戶端經過 AIDL 接口的靜態方法asInterface 將 Binder 對象轉化成 AIDL 接口的代理對象,經過這個代理對象就能夠發起遠程調用請求了。 用這張圖來表述再清楚不過了. 值得一提的是, 在這裏AIDL是系統自動生成的, 實際上咱們手寫AIDL也是徹底能夠的, 只要徹底理解Binder的原理, 只不過使用系統生成更加方便快速而已. 下面總結一下幾種常見的IPC方式

名稱 優勢 缺點 適用場景
Bundle 簡單易用 只能傳輸Bundle支持的數據類型 四大組件的進程間通訊
文件共享 簡單易用 不適合高併發場景, 而且沒法作到進程間的即時通訊 無併發訪問情形, 交換簡單的數據, 實時性不高的場景
AIDL 功能強大, 支持一對多併發通訊, 支持實時通訊 使用稍複雜, 須要處理好線程同步 一對多通訊且有RPC需求
Messenger 功能通常, 支持一對多串行通訊, 支持實時通訊 不能很好處理高併發情形, 不支持RPC, 數據經過Message進行傳輸, 所以只能傳輸Bundle支持的數據類型 低併發的一對多即時通訊, 無RPC需求, 或者無須要返回結果的RPC需求
ContentProvider 在數據源訪問方便功能強大, 支持一對多併發數據共享, 可經過Call方法擴展其餘操做 能夠理解爲受約束的AIDL, 主要提供數據源的CURD操做 一對多的進程間的數據共享
Socket 功能強大, 能夠經過網絡傳輸字節流, 支持一對多併發實時通訊 實現細節有點繁瑣, 不支持直接的RPC 網絡數據交換

關於Binder, 若是想了解更多一些, 我推薦weishu的這一篇Binder 學習指南

demo地址

github.com/mundane7996…

相關文章
相關標籤/搜索