溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android進(jìn)階之AIDL的使用詳解

發(fā)布時(shí)間:2020-04-01 08:49:59 來源:網(wǎng)絡(luò) 閱讀:1706 作者:零點(diǎn)小筑 欄目:移動(dòng)開發(fā)

原文首發(fā)于微信公眾號(hào):jzman-blog,歡迎關(guān)注交流!

AIDL(Android 接口定義語言),可以使用它定義客戶端與服務(wù)端進(jìn)程間通信(IPC)的編程接口,在 Android 中,進(jìn)程之間無法共享內(nèi)存(用戶空間),不同進(jìn)程之間的通信一般使用 AIDL 來處理。

主要流程就是在 .aidl 文件中定義 AIDL 接口,并將其添加到應(yīng)用工程的 src 目錄下,創(chuàng)建完成之后 rebuild,Android SDK 工具會(huì)自動(dòng)生成基于該 .aidl 文件的 IBinder 接口,具體的業(yè)務(wù)對(duì)象實(shí)現(xiàn)這個(gè)接口,這個(gè)具體的業(yè)務(wù)對(duì)象也是 IBinder 對(duì)象,當(dāng)綁定服務(wù)的時(shí)候會(huì)根據(jù)實(shí)際情況返回具體的通信對(duì)象(本地還是代理),最后
將客戶端綁定到該服務(wù)上,之后就可以調(diào)用 IBinder 中的方法來進(jìn)行進(jìn)程間通信(IPC),下面將從以下幾個(gè)方面學(xué)習(xí) AIDL 的使用:

  1. 創(chuàng)建.aildl 文件
  2. 具體的業(yè)務(wù)對(duì)象實(shí)現(xiàn)基于 .aidl 文件生成的接口
  3. 向客戶端公開接口
  4. 客戶端遠(yuǎn)程調(diào)用
  5. 驗(yàn)證 AIDL
創(chuàng)建.aildl 文件

在 AIDL 中可以通過可帶參數(shù)以及返回值的一個(gè)或多個(gè)方法來聲明接口,參數(shù)和返回值可以是任意類型,AIDL 中支持的數(shù)據(jù)類型如下:

  1. java 的 8 種數(shù)據(jù)類型:byte、short、int、long、float、double、boolean、char
  2. 除此之外支持 String、charSequence、List、Map
  3. 自定義數(shù)據(jù)類型

如果業(yè)務(wù)方法中參數(shù)或返回值類型為 List 或 Map 時(shí):

List 中的所有元素都必須是 AIDL 支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或自己聲明的可打包類型。可選擇將 List 用作“通用”類(例如,List<String>)。另一端實(shí)際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 接口。

Map 中的所有元素都必須是 AIDL 支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端實(shí)際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口。

當(dāng)然,AIDL 也支持自定義數(shù)據(jù)類型,會(huì)在下文中介紹。

首先,在工程的 src 目錄下創(chuàng)建 .aidl 文件,具體如下圖所示:

Android進(jìn)階之AIDL的使用詳解

然后,在 .aidl 文件中添加具體的業(yè)務(wù)方法,文件內(nèi)容如下:

// IPersonAidlInterface.aidl
package com.manu.aidldemo;
// Declare any non-default types here with import statements
interface IPersonAidlInterface {
    //具體的業(yè)務(wù)
    void setName(String name);
    void setAge(int age);
    String getInfo();

    /**
     * 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);
}

然后,重新 rebuild project , Android SDK 工具會(huì)在相應(yīng)的目錄生成對(duì)應(yīng)的與 .aidl 文件同名的 .java 接口文件,具體目錄如下:

Android進(jìn)階之AIDL的使用詳解

具體的業(yè)務(wù)對(duì)象實(shí)現(xiàn)基于 .aidl 文件生成的接口

上一步只是使用 AIDL 定義了基本的業(yè)務(wù)操作,rebuild 之后會(huì)生成與 .aidl 相同文件名的 .java 文件,生成的這個(gè)接口文件中有一個(gè)名稱為 Stub 的一個(gè)子類,這個(gè)子類也是其父接口的抽象實(shí)現(xiàn),主要用于生成 .aidl 文件中的所有方法,Stub 類聲明具體如下:

// Stub
public static abstract class Stub extends android.os.Binder implements com.manu.aidldemo.IPersonAidlInterface

顯然,Stub 實(shí)現(xiàn)了本地接口且繼承了 Binder 對(duì)象,介于 Binder 對(duì)象在系統(tǒng)底層的支持下,Stub 對(duì)象就具有了遠(yuǎn)程傳輸數(shù)據(jù)的能力,在生成 Stub 對(duì)象的時(shí)候會(huì)調(diào)用 asInterface 方法,具體如下:

// asInterface
public static com.manu.aidldemo.IPersonAidlInterface asInterface(android.os.IBinder obj){

    if ((obj==null)) {
        return null;
    }

    // 檢索 Binder 對(duì)象是否是本地接口的實(shí)現(xiàn)
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

    if (((iin!=null)&&(iin instanceof com.manu.aidldemo.IPersonAidlInterface))) {
        return ((com.manu.aidldemo.IPersonAidlInterface)iin);
    }

    return new com.manu.aidldemo.IPersonAidlInterface.Stub.Proxy(obj);
}

asInterface 這個(gè)方法在 Stub 創(chuàng)建的時(shí)候會(huì)調(diào)用,主要功能就是檢索 Binder 對(duì)象是否是本地接口的實(shí)現(xiàn),根據(jù) queryLocalInterface() 方法返回值判斷是否使用代理對(duì)象,這個(gè)檢索過程應(yīng)該由系統(tǒng)底層支持,如果返回為 null,則創(chuàng)建 Stub 的代理對(duì)象,反之使用本地對(duì)象來傳輸數(shù)據(jù),下面看一下 Binder 為什么具有遠(yuǎn)程通信的能力,因?yàn)?Stub 繼承了 Binder 類,Binder 類具體如下:

// Binder
public class Binder implements IBinder {
    //...
}

下面是官網(wǎng)對(duì) IBinder 接口的描述:

遠(yuǎn)程對(duì)象的基礎(chǔ)接口,輕量級(jí)遠(yuǎn)程過程調(diào)用機(jī)制的核心部分,專為執(zhí)行進(jìn)程內(nèi)和跨進(jìn)程調(diào)用時(shí)的高性能而設(shè)計(jì)。該接口描述了與可遠(yuǎn)程對(duì)象交互的抽象協(xié)議。不要直接實(shí)現(xiàn)這個(gè)接口,而是從Binder擴(kuò)展。

這里我們知道 Binder 實(shí)現(xiàn)了 IBinder 接口,也就是說 Binder 具備了遠(yuǎn)程通信的能力,當(dāng)不同進(jìn)程之間(遠(yuǎn)程)之間通信時(shí),顯然使用的是 Stub 的代理對(duì)象,這個(gè)代理類里面具體的業(yè)務(wù)處理邏輯,如下面這個(gè)方法,具體如下:


//具體的業(yè)務(wù)
@Override 
public void setName(java.lang.String name) throws android.os.RemoteException{
    // 將數(shù)據(jù)序列化
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(name);
        // 這個(gè)方法會(huì)最終調(diào)用 onTransact 方法
        mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

這里主要就是將數(shù)據(jù)序列化,然后在系統(tǒng)跨進(jìn)程支持下最終調(diào)用 onTransact() 方法,下面是 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_setName:
        {
            //...
            // 最終調(diào)用了 Stub 里面的業(yè)務(wù)方法
            this.setName(_arg0);
            //...
        }
    }
}

顯然,這個(gè)方法在當(dāng)系統(tǒng)回調(diào)給開發(fā)者的時(shí)候,傳遞回來的 code 是一個(gè)常量,在跨進(jìn)程時(shí),每個(gè)具體的服務(wù)(方法)都會(huì)對(duì)應(yīng)一個(gè)編號(hào),然后根據(jù)這個(gè)編號(hào)來執(zhí)行相應(yīng)的服務(wù)(業(yè)務(wù)),這里說到了最后要執(zhí)行的具體業(yè)務(wù),那么這個(gè)業(yè)務(wù)要體現(xiàn)在什么地方呢,從上面可知 Stub 是一個(gè)抽象類,那么它所提供的具體業(yè)務(wù)必然需要一個(gè)具體的實(shí)現(xiàn)類來完成,下面實(shí)現(xiàn)這個(gè)具體的業(yè)務(wù)類,具體如下:

/**
 * Created by jzman
 * Powered by 2018/3/8 0008.
 */
public class IPersonImpl extends IPersonAidlInterface.Stub {
    private String name;
    private int age;

    @Override
    public void setName(String name) throws RemoteException {
        this.name = name;
    }

    @Override
    public void setAge(int age) throws RemoteException {
        this.age = age;
    }

    @Override
    public String getInfo() throws RemoteException {
        return "My name is "+name+", age is "+age+"!";
    }

    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

    }
}

這個(gè)類就是對(duì)外提供的具體業(yè)務(wù)類,同時(shí)其實(shí)例也是一個(gè) Binder 對(duì)象。

向客戶端公開接口

創(chuàng)建一個(gè) Service 以便對(duì)外提供具體的業(yè)務(wù),具體如下:

// Service
public class PersonService extends Service {
    public PersonService() {
    }

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

當(dāng)外部調(diào)用 bindService() 方法綁定服務(wù)時(shí),就會(huì)調(diào)用 onBind() 方法返回 IBinder 對(duì)象,這個(gè) IBinder 對(duì)象也是具體的業(yè)務(wù)對(duì)象,如這里的 onBind() 方法返回的也是具體的業(yè)務(wù)對(duì)象,兩者是統(tǒng)一的。此外,創(chuàng)建的 Service 要在 AndroidManifest.xml 文件中聲明,具體如下:

<service
    android:name=".PersonService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
</service>

其中使用 process 關(guān)鍵字表示為該服務(wù)開啟一個(gè)獨(dú)立的進(jìn)程,remote 可任意,表示進(jìn)程名稱,":"將會(huì)在主進(jìn)程(進(jìn)程名為包名)添加新名稱作為新進(jìn)程的名稱,如 com.manu.study 將會(huì)變成 com.manu.study:remote。

客戶端遠(yuǎn)程調(diào)用

通過上面幾步完成了服務(wù)的搭建,并將服務(wù)運(yùn)行在獨(dú)立進(jìn)程中,下面主要就是客戶端的具體調(diào)用了,具體實(shí)現(xiàn)參考如下:

// Client
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private IPersonAidlInterface iPersonAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void bindServiceClick(View view) {
        Log.i(TAG,"綁定服務(wù)...");
        Intent intent = new Intent(this,PersonService.class);
        // 綁定服務(wù)時(shí)自動(dòng)創(chuàng)建服務(wù)
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }

    public void unbindServiceClick(View view) {
        Log.i(TAG,"解綁服務(wù)...");
        unbindService(conn);
    }

    public void callRemoteClick(View view) {
        Log.i(TAG,"遠(yuǎn)程調(diào)用具體服務(wù)...");
        try {
            iPersonAidlInterface.setName("Tom");
            iPersonAidlInterface.setAge(10);
            String info = iPersonAidlInterface.getInfo();
            System.out.println("這是遠(yuǎn)程調(diào)用的服務(wù)信息:"+info);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 根據(jù)實(shí)際情況返回 IBinder 的本地對(duì)象或其代理對(duì)象
            iPersonAidlInterface = IPersonAidlInterface.Stub.asInterface(service);
            System.out.println("具體的業(yè)務(wù)對(duì)象:"+iPersonAidlInterface);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // Service 意外中斷時(shí)調(diào)用
        }
    };
}

上述代碼是客戶端調(diào)用具體服務(wù)的過程。

驗(yàn)證 AIDL

通過前面幾步,服務(wù)端與客戶端已經(jīng)完成,下面來驗(yàn)證能否調(diào)用具體的業(yè)務(wù),這里分兩種情況:

1. 相同進(jìn)程

創(chuàng)建 Service 的時(shí)候不要在 AndroidManifest.xml 文件中不要使用 process 開啟獨(dú)立進(jìn)程即可,此時(shí)服務(wù)進(jìn)程默認(rèn)與客戶端屬于統(tǒng)一進(jìn)程,結(jié)果如下:

Android進(jìn)階之AIDL的使用詳解

2. 不同進(jìn)程

創(chuàng)建 Service 的時(shí)候在 AndroidManifest.xml 文件中使用 process 開啟獨(dú)立進(jìn)程即可,這個(gè)在上文中提到過,此時(shí),服務(wù)進(jìn)程與客戶端進(jìn)程位于不同進(jìn)程,結(jié)果如下:

Android進(jìn)階之AIDL的使用詳解

顯然,如果服務(wù)與客戶端處于不同進(jìn)程,也就是常常說的進(jìn)程間通信,具體是由 IBinder 對(duì)象的代理對(duì)象完成,反之,使用本地對(duì)象,也就是本地的 IBinder 對(duì)象,具體就是實(shí)現(xiàn) AIDL 的業(yè)務(wù)類所生成的對(duì)象來完成。下篇文章將介紹如何在 AIDL 中使用自定義類型。

最后

Android進(jìn)階之AIDL的使用詳解

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI