AIDL使用詳解及原理

我們都知道,在Android中,系統(tǒng)會(huì)為每個(gè)進(jìn)程分配對(duì)應(yīng)的內(nèi)存空間,這部分內(nèi)存是彼此間相互獨(dú)立,不可直接交互的,這樣的設(shè)計(jì)是處于安全性以及系統(tǒng)穩(wěn)定性方面考慮的,比如當(dāng)我們的App奔潰時(shí),不至于導(dǎo)致其他App無(wú)法運(yùn)行,甚至死機(jī)等情況。那么,Android中是否就無(wú)法實(shí)現(xiàn)進(jìn)程間通信呢?答案當(dāng)然是否定的。Android中進(jìn)程通信的方式有很多,比如AIDL就可以實(shí)現(xiàn)這樣子的需求。


進(jìn)程間通信.png

1.AIDL

? AIDL(Android Interface Define Language)是一種IPC通信方式,我們可以利用它來(lái)定義兩個(gè)進(jìn)程相互通信的接口。他是基于Service實(shí)現(xiàn)的一種線程間通信機(jī)制。它的本質(zhì)是C/S架構(gòu)的,需要一個(gè)服務(wù)器端,一個(gè)客戶端。

2.AIDL的使用

2.1創(chuàng)建aidl

? 首先我們?cè)贏ndroidStudio中創(chuàng)建一個(gè)Andorid工程,

? 隨后添加一個(gè)module,作為aidl的服務(wù)端

? 在aidlserver中創(chuàng)建aild目錄, 同時(shí)創(chuàng)建一個(gè)aidl文件

aidlserver目錄.png
// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;

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

interface IMyAidlInterface {

    /**
    * 自己添加的方法
    */
    int add(int value1, int value2);
}

? 這邊可以看到aidl的語(yǔ)法跟JAVA是一樣的,聲明了一個(gè)接口,里面定義了aidl服務(wù)器端暴露給客戶端調(diào)用的方法。

? 完成這部分操作之后還沒(méi)有結(jié)束,我們需要手動(dòng)編譯程序,生成aidl對(duì)應(yīng)的Java代碼

aidl生成過(guò)程.png
2.2實(shí)現(xiàn)接口,并向客戶端放開(kāi)接口
public class MyAidlService extends Service {
    public MyAidlService() {
    }

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

    private IBinder iBinder = new IMyAidlInterface.Stub(){
        @Override
        public int add(int value1, int value2) throws RemoteException {
            return value1 + value2;
        }
    };
}

? 我們創(chuàng)建了一個(gè)service,并在service內(nèi)部聲明了一個(gè)IBinder對(duì)象,它是一個(gè)匿名實(shí)現(xiàn)的IMyAidlInterface.Stub的實(shí)例(這部分我們后面講),同時(shí)我們?cè)诎l(fā)現(xiàn)IMyAidlInterface.Stub實(shí)例實(shí)現(xiàn)了add方法,這個(gè)方法正是我們?cè)赼idl中聲明的供客戶端調(diào)用的方法。

2.3客戶端調(diào)用aidl

? 首先在客戶端跟服務(wù)器一樣,新建aidl目錄,將服務(wù)器端的aidl拷貝到客戶端,這邊特別要注意,拷貝后的客戶端的aidl文件包目錄必須與服務(wù)器端保持一致,拷貝完后同樣時(shí)編譯工程,讓客戶端也生成對(duì)應(yīng)的java文件

客戶端使用aidl_目錄.png

? 其次就是在Activity的onCreate中綁定服務(wù)


 private ServiceConnection connection = new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //綁定服務(wù)成功回調(diào)
           aidl = IMyAidlInterface.Stub.asInterface(service);
       }

       @Override
       public void onServiceDisconnected(ComponentName name) {
           //服務(wù)斷開(kāi)時(shí)回調(diào)
           aidl = null;
       }
   };

@Override
protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      //do something
      bindService();
}

private void bindService(){
        Intent intent = new Intent();
        //Android 5.0開(kāi)始,啟動(dòng)服務(wù)必須使用顯示的,不能用隱式的
        intent.setComponent(new ComponentName("com.yunzhou.aidlserver", "com.yunzhou.aidlserver.MyAidlService"));
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

? 綁定完服務(wù),就是進(jìn)行調(diào)用了,具體的頁(yè)面細(xì)節(jié)這邊不做展示,就是在Activity中放了一個(gè)按鈕,點(diǎn)擊按鈕進(jìn)行遠(yuǎn)程調(diào)用

int result = aidl.add(12, 12);
Log.e(TAG, "遠(yuǎn)程回調(diào)結(jié)果:" + result);
遠(yuǎn)程調(diào)用結(jié)果.png

? 可以看到,logcat打印出來(lái)結(jié)果,說(shuō)明遠(yuǎn)程調(diào)用成功了,至此aidl的整個(gè)流程就走完了。

3.AIDL可使用的參數(shù)類型

3.1基本數(shù)據(jù)類型

我們都知道Java有8中基本數(shù)據(jù)類型,分別為byte,char,short,int,long,float,double,boolean,那這8中數(shù)據(jù)類型是否都能作為aidl的參數(shù)進(jìn)行傳遞呢?我們可以在aild中嘗試下,并編譯,看看有沒(méi)有錯(cuò)

void basicTypes(byte aByte, char aChar, short aShort, int anInt, long aLong, float aFloat,
            double aDouble, boolean aBoolean);

發(fā)現(xiàn)這樣子定義,無(wú)法成功編譯,經(jīng)過(guò)篩查發(fā)現(xiàn)aidl并不能支持short基本數(shù)據(jù)類型,至于為什么呢,可以看一看Android中的Parcel,Parcel是不支持short的,這應(yīng)該是考慮到兼容性問(wèn)題吧。

所以基本數(shù)據(jù)類型支持:byte,char,int,long,float,double,boolean

3.2引用數(shù)據(jù)類型

引用數(shù)據(jù)類型根據(jù)官方介紹,可以使用String,CharSequence,List,Map,當(dāng)然,我們也可以使用自定義數(shù)據(jù)類型。

3.3自定義數(shù)據(jù)類型

自定義數(shù)據(jù)類型,用于進(jìn)程間通信的話,必須實(shí)現(xiàn)Parcelable接口,Parcelable是類似于Java中的Serializable,Android中定義了Parcelable,用于進(jìn)程間數(shù)據(jù)傳遞,對(duì)傳輸數(shù)據(jù)進(jìn)行分解,編組的工作,相對(duì)于Serializable,他對(duì)于進(jìn)程間通信更加高效。

我們來(lái)看下下面的例子

public class User implements Parcelable {

    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public User(Parcel in){
      //注意順序?。?!注意順序?。?!注意順序?。?!
        this.id = in.readInt();
        this.name = in.readString();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      //注意順序?。?!注意順序!?。∽⒁忭樞颍。?!
        dest.writeInt(id);
        dest.writeString(name);
    }

    public static final  Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

? 這邊我們定義了一個(gè)User類,實(shí)現(xiàn)了Parcelable接口,大致的類結(jié)構(gòu)就是這個(gè)樣子的,需要注意的一點(diǎn)是,Parcelable對(duì)數(shù)據(jù)進(jìn)行分解/編組的時(shí)候必須使用相同的順序,字段以什么順序分解的,編組時(shí)就以什么順序讀取數(shù)據(jù),不然會(huì)有問(wèn)題!

? 創(chuàng)建完實(shí)體后,我們需要?jiǎng)?chuàng)建一個(gè)aidl文件,來(lái)定義一下我們的User,否則User在aidl中無(wú)法識(shí)別

// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;

parcelable User;

? 并在之前的服務(wù)器端aidl中新增方法

interface IMyAidlInterface {
    int add(int value1, int value2);
    List<User> addUser(in User user);
}

? 在service中實(shí)現(xiàn)新增的addUser方法

private ArrayList users;

@Override
public IBinder onBind(Intent intent) {
  users = new ArrayList<User>();
  return iBinder;
}

private IBinder iBinder = new IMyAidlInterface.Stub(){
  @Override
  public int add(int value1, int value2) throws RemoteException {
    return value1 + value2;
  }

  @Override
  public List<User> addUser(User user) throws RemoteException {
    users.add(user);
    return users;
  }
};

? 此時(shí),server端的目錄就夠如下

parcelable_服務(wù)端結(jié)構(gòu).png

? 服務(wù)器端一切準(zhǔn)備就緒后,我們對(duì)客戶端進(jìn)行操作,首先,我們將服務(wù)端的兩個(gè)aidl文件復(fù)制到客戶端,包結(jié)構(gòu)必須一致,aidl文件發(fā)生變化不要忘記重新編譯代碼。

? 然后,將User實(shí)體也復(fù)制到客戶端,并且包結(jié)構(gòu)一致。

? 最后,在客戶端進(jìn)行addUser的操作(這邊只是添加了一個(gè)按鈕,每點(diǎn)擊一次就調(diào)用一次addUser)

try {
  ArrayList<User> users = (ArrayList<User>) aidl.addUser(new User(12, "demaxiya"));
  Log.e(TAG, "遠(yuǎn)程回調(diào)結(jié)果:" + users.toString());
} catch (RemoteException e) {
  e.printStackTrace();
}

? 此時(shí)客戶端的目錄結(jié)構(gòu)如下:


parcelable_客戶端結(jié)構(gòu).png

? 運(yùn)行服務(wù)端與客戶端App,點(diǎn)擊addUser,輸出日下日志,說(shuō)明調(diào)用成功


parcelable_遠(yuǎn)程調(diào)用結(jié)果.png

4.AIDL原理

? 要了解aidl原理,我們需要看一下根據(jù)aidl生成的對(duì)應(yīng)的java代碼了,

public interface IMyAidlInterface extends android.os.IInterface{
    public static abstract class Stub extends android.os.Binder implements com.yunzhou.aidlserver.IMyAidlInterface{...}
    public int add(int value1, int value2) throws android.os.RemoteException;
    public java.util.List<com.yunzhou.aidlserver.User> addUser(com.yunzhou.aidlserver.User user) throws android.os.RemoteException;
}

? 我們可以看到,生成的代碼結(jié)構(gòu)很簡(jiǎn)單,一個(gè)靜態(tài)抽象類Stub,以及aidl中定義的方法,其中Stub肯定時(shí)核心,我們深入閱讀

Stub.png

? Stub的目錄結(jié)構(gòu)也不復(fù)雜,一個(gè)構(gòu)造函數(shù),一個(gè)asInterface方法,一個(gè)asBinder方法,一個(gè)onTransact方法,一個(gè)Proxy代理類,這邊Proxy與Stub同時(shí)實(shí)現(xiàn)了我們定義的aidl,且Proxy中實(shí)現(xiàn)了我們?cè)赼idl中定義的add/addUser方法。下面兩個(gè)int為告訴系統(tǒng)的方法Id

? 在客戶端,我們綁定服務(wù)的時(shí)候通過(guò)Stub.asInterface()回去aidl對(duì)象,查看asInterface源碼,我們不難發(fā)現(xiàn),客戶端獲取到的其實(shí)時(shí)Stub.Proxy,一個(gè)遠(yuǎn)程服務(wù)的代理。

//客戶端獲取aidl
aidl = IMyAidlInterface.Stub.asInterface(service);

//Stub的asInterface
public static com.yunzhou.aidlserver.IMyAidlInterface asInterface(android.os.IBinder obj) {
  if ((obj==null)) {
    return null;
  }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof com.yunzhou.aidlserver.IMyAidlInterface))) {
    return ((com.yunzhou.aidlserver.IMyAidlInterface)iin);
  }
  return new com.yunzhou.aidlserver.IMyAidlInterface.Stub.Proxy(obj);
}

? 所以客戶端調(diào)用add/addUser方法其實(shí)調(diào)用的時(shí)Stub.Proxy中實(shí)現(xiàn)的add/addUser,在Stub.Proxy中我們可以看到一句核心代碼

//add
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

//addUser
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);

? 追溯源頭,mRemote其實(shí)就是IMyAidlInterface.Stub,隨意mRemote.transact傳遞到了IMyAidlInterface.Stub.OnTransact, onTransact中執(zhí)行了add/addUser,回調(diào)到了我們?cè)诜?wù)端定義Service中聲明IBidner時(shí)重寫(xiě)的add/addUser,這就是AIDL的整個(gè)流程。

private IBinder iBinder = new IMyAidlInterface.Stub(){
  @Override
  public int add(int value1, int value2) throws RemoteException {
    return value1 + value2;
  }

  @Override
  public List<User> addUser(User user) throws RemoteException {
    users.add(user);
    return users;
  }
};

? 下面用一張圖來(lái)總結(jié)aidl原理,這邊特別感謝imooc

aidl原理.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Jianwei's blog 首頁(yè) 分類 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程,微信,微博等主流App都在用...
    justCode_閱讀 6,111評(píng)論 1 23
  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 10,966評(píng)論 13 43
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 本篇包含進(jìn)程間通信——AIDL所涉及到的知識(shí)的自我總結(jié)(內(nèi)容詳細(xì)) 通過(guò)前段時(shí)間對(duì)AIDL的學(xué)習(xí)以及最近一些資料的...
    arvinljw閱讀 3,278評(píng)論 0 17
  • 三文魚(yú)去骨切塊,平底鍋燒熱放入魚(yú)塊(不要放油),中小火煎至兩面微黃取出。 四季豆、秋葵、豆豉、青椒切碎,姜絲、蒜切...
    北美K哥閱讀 516評(píng)論 0 2

友情鏈接更多精彩內(nèi)容