Android IPC 之AIDL應(yīng)用(上)

前言

IPC 系列文章:
建議按順序閱讀。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎(chǔ)
Android IPC 之Binder應(yīng)用
Android IPC 之AIDL應(yīng)用(上)
Android IPC 之AIDL應(yīng)用(下)
Android IPC 之Messenger 原理及應(yīng)用
Android IPC 之服務(wù)端回調(diào)
Android IPC 之獲取服務(wù)(IBinder)
Android Binder 原理?yè)Q個(gè)姿勢(shì)就頓悟了(圖文版)

上一篇文章分析了如何使用Binder進(jìn)行進(jìn)程間通信以及提出了直接使用Binder編碼的缺點(diǎn),本篇闡述如何使用AIDL解決以上缺點(diǎn)。
通過(guò)本篇文章,你將了解到:

1、如何編寫AIDL 文件
2、如何使用AIDL

1、如何編寫AIDL 文件

什么是AIDL

AIDL 是Android Interface Definition Language (Android 接口定義語(yǔ)言)的縮寫。

創(chuàng)建AIDL 文件

Android Studio本身支持創(chuàng)建AIDL文件,先創(chuàng)建名為IMyServer 的AIDL文件。
在Module上右鍵單擊:


image.png

輸入名字:


image.png

確定后生成 IMyServer.aidl文件:
image.png

可以看出,由于是第一次創(chuàng)建AIDL文件,因此還創(chuàng)建了aidl文件夾并添加了包名作為目錄結(jié)構(gòu),其總體結(jié)構(gòu)如下:

src/main/aidl/com/fish/myapplication/IMyServer.aidl

其中/aidl目錄與/java、/res目錄平級(jí),都在main目錄下:

app/src/main/
app/src/aidl/
app/src/res

AIDL 文件內(nèi)容

生成IMyServer.aidl內(nèi)容如下:

//包名
package com.fish.myapplication;

interface IMyServer {
    //aidl 支持的基本數(shù)據(jù)類型
    //默認(rèn)生成的方法,可以去掉
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

其中basicTypes(xx)方法是自動(dòng)生成的,用來(lái)指導(dǎo)我們?nèi)绾尉帉懛椒?,可以去掉?br> IMyServer 接口里聲明的方法為Server端暴露給外部調(diào)用的方法,先為Server添加方法:


//包名
package com.fish.myapplication;

interface IMyServer {
    //只有一個(gè)參數(shù),并且沒(méi)有返回值
    void say(String word);
    
    //有兩個(gè)參數(shù),并且返回int
    int tell(String word, int age);
}

然后編譯工程。

AIDL 編譯產(chǎn)物

編譯成功后,切換到Project模式,搜索IMyServer.java:


image.png

可以看出,編寫的AIDL文件,最終根據(jù)一定的規(guī)則映射生成Java文件,接著來(lái)看看IMyServer.java內(nèi)容。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.fish.myapplication;
public interface IMyServer extends android.os.IInterface
{
  //默認(rèn)類實(shí)現(xiàn)接口,可以不用關(guān)注
  public static class Default implements com.fish.myapplication.IMyServer
  {
    @Override public void say(java.lang.String word) throws android.os.RemoteException
    {
    }
    @Override public int tell(java.lang.String word, int age) throws android.os.RemoteException
    {
      return 0;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }


  public static abstract class Stub extends android.os.Binder implements com.fish.myapplication.IMyServer
  {
    //描述符
    private static final java.lang.String DESCRIPTOR = "com.fish.myapplication.IMyServer";
    public Stub()
    {
      //調(diào)用Binder方法,將Binder與IInterface 關(guān)聯(lián)起來(lái)
      //也就是說(shuō)Binder持有IInterface引用
      this.attachInterface(this, DESCRIPTOR);
    }

    //通過(guò)Binder找到關(guān)聯(lián)的IInterface
    public static com.fish.myapplication.IMyServer asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.fish.myapplication.IMyServer))) {
        //IBinder引用與調(diào)用者同一進(jìn)程,直接返回IInterface
        return ((com.fish.myapplication.IMyServer)iin);
      }
      //不同進(jìn)程則返回Proxy,并傳入Binder
      return new com.fish.myapplication.IMyServer.Stub.Proxy(obj);
    }
    //返回自身
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    
    //重寫onTransact(xx)
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        //根據(jù)code,調(diào)用不同的方法
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_say:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          //反序列化,讀取數(shù)據(jù)
          _arg0 = data.readString();
          this.say(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_tell:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.tell(_arg0, _arg1);
          reply.writeNoException();
          //寫入回復(fù)
          reply.writeInt(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.fish.myapplication.IMyServer
    {
      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 void say(java.lang.String word) throws android.os.RemoteException
      {
        //構(gòu)造序列化數(shù)據(jù)
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          //寫入序列化
          _data.writeString(word);
          //mRemote為遠(yuǎn)程的IBinder
          boolean _status = mRemote.transact(Stub.TRANSACTION_say, _data, _reply, 0);
          //阻塞等待transact(xx)調(diào)用結(jié)果
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().say(word);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public int tell(java.lang.String word, int age) 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.writeString(word);
          _data.writeInt(age);
          boolean _status = mRemote.transact(Stub.TRANSACTION_tell, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().tell(word, age);
          }
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.fish.myapplication.IMyServer sDefaultImpl;
    }
    static final int TRANSACTION_say = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_tell = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.fish.myapplication.IMyServer impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.fish.myapplication.IMyServer getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  
  //聲明的公共方法
  public void say(java.lang.String word) throws android.os.RemoteException;
  public int tell(java.lang.String word, int age) throws android.os.RemoteException;
}

代碼不長(zhǎng),但是初看起來(lái)云里霧里的,如果不怎么熟悉以上內(nèi)容,強(qiáng)烈建議先閱讀上篇文章:Android IPC 之Binder應(yīng)用,再看這篇就很容易了。

提取重點(diǎn)分析上面的代碼。
定義接口
定義了IMyServer 接口,該接口里的方法就是根據(jù)IMyServer.aidl里聲明的方法生成的。

兩個(gè)靜態(tài)類
Stub是抽象類。
繼承了Binder,重寫了onTransact(xx)方法。
實(shí)現(xiàn)了IMyServer 接口,并沒(méi)有實(shí)現(xiàn)里面的方法,這些方法待服務(wù)端實(shí)現(xiàn)。
當(dāng)onTransact(xx)被調(diào)用的時(shí)候,根據(jù)不同的code調(diào)用相應(yīng)的方法。
在上篇文章里分析的時(shí)候,onTransact(xx)與IMyServer 接口是分離的,我們需要手動(dòng)在onTransact(xx)里調(diào)用IMyServer 方法。而此時(shí)Stub將兩者結(jié)合起來(lái)了,完成了服務(wù)端與Binder驅(qū)動(dòng)的聯(lián)動(dòng)。

Proxy雖然沒(méi)有繼承自Binder,但是持有IBinder引用:mRemote。
實(shí)現(xiàn)了IMyServer,并且實(shí)現(xiàn)了其所有方法,每個(gè)方法里最終都通過(guò)mRemote調(diào)用transact(xx)完成了客戶端與Binder驅(qū)動(dòng)聯(lián)動(dòng)。

至此,通過(guò)這兩個(gè)類,分別完成了服務(wù)端、客戶端與Binder的聯(lián)動(dòng)。
繼續(xù)引用上篇的圖:


image.png

可以看出,有了AIDL自動(dòng)生成的類后:

1、繁雜的switch case 不用自己編寫了
2、序列化反序列化也不用編寫了
3、不再需要編寫transact(xx)與onTransact(xx)了

極大解放了生產(chǎn)力。

2、如何使用AIDL

既然IMyServer.java 已經(jīng)生成了,繼續(xù)來(lái)看看如何使用它。

編寫Server端業(yè)務(wù)

public class MyService extends Service {

    private final String TAG = "IPC";

    //構(gòu)造內(nèi)部類
    private IMyServer.Stub stub = new IMyServer.Stub() {
        @Override
        public void say(String word) throws RemoteException {
            Log.d(TAG, "receive say content:" + word + " in server");
        }

        @Override
        public int tell(String word, int age) throws RemoteException {
            Log.d(TAG, "receive tell content:" + word + " age:" + age + " in server");
            return age + 1;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //Stub 繼承自Binder,因此是IBinder類型
        return stub;
    }
}

首先實(shí)現(xiàn)業(yè)務(wù)接口。
其次在onBind(xx)里將Binder返回給客戶端。
業(yè)務(wù)邏輯實(shí)現(xiàn)了,等待客戶端調(diào)用。

編寫客戶端業(yè)務(wù)

先定義ServiceConnection:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyServer iMyServer = IMyServer.Stub.asInterface(service);
            try {
                iMyServer.say("how are you?");
                int result = iMyServer.tell("how are you?", 18);
                Log.d("IPC", "receive return content:" + result + " in client");
            } catch (Exception e) {

            }
        }

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

service 為IBinder引用,該引用從服務(wù)端經(jīng)過(guò)Binder驅(qū)動(dòng)傳遞而來(lái)(不一定是同一個(gè)引用)。
IMyServer.Stub.asInterface(service) 用來(lái)尋找該IBinder對(duì)應(yīng)的服務(wù)端提供的接口。

1、當(dāng)IBinder與調(diào)用者同一進(jìn)程,則IBinder為Binder類型,即為自身定義的Stub。
2、當(dāng)IBinder與調(diào)用者不是同一進(jìn)程,則IBinder為BinderProxy類型(為什么是這個(gè)類型,后續(xù)文章會(huì)分析)。

此處測(cè)試的是兩個(gè)不同的進(jìn)程,因此IBinder service指向BinderProxy。
在ServiceConnection里,當(dāng)綁定成功后調(diào)用Proxy里的方法,其內(nèi)部通過(guò)BinderProxy調(diào)用transact(xx)。

上面的邏輯都寫了,最后當(dāng)然需要綁定Service:

    private void bindService() {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

來(lái)看看打印結(jié)果:


image.png

可以看出通信成功了。

需要注意的是:

以上Demo都是同一個(gè)工程里編寫的,因此客戶端、服務(wù)端都能訪問(wèn)IMyServer.Stub,若是在不同的進(jìn)程,需要寫同樣的IMyServer.aidl文件。

讓Service在不同的進(jìn)程運(yùn)行只需要在AndroidManifest.xml添加如下字段:

        <service android:name=".MyService" android:process=":aidl">
        </service>

綁定后,運(yùn)行的兩個(gè)進(jìn)程如下:


image.png

總結(jié)AIDL用法

通過(guò)以上Demo可以看出,通過(guò)編寫AIDL實(shí)現(xiàn)IPC。
服務(wù)端僅需要兩步:
第一步
實(shí)現(xiàn)接口對(duì)應(yīng)方法的業(yè)務(wù)邏輯

第二步
在onBind(xx)里將接口關(guān)聯(lián)的Binder返回

同樣的客戶端調(diào)用服務(wù)端僅僅只需要兩步:
第一步
通過(guò)Stub拿到服務(wù)端的接口

第二步
拿到接口后調(diào)用對(duì)應(yīng)的方法

image.png

明顯的,IMyServer.java 已經(jīng)為我們實(shí)現(xiàn)了連接Binder的功能,屏蔽了對(duì)接的Binder細(xì)節(jié)??蛻舳苏{(diào)用服務(wù)端的方法(Proxy)與服務(wù)端進(jìn)行通信,就像是"直接"調(diào)用一般,符合Java一貫的面向?qū)ο蟮乃季S。

結(jié)合上一篇文章對(duì)Binder應(yīng)用的分析,以及本篇AIDL的分析,我們知道:

  • AIDL 并不是我們熟知的Java、C++語(yǔ)言,而是一種規(guī)范。按此規(guī)范編寫的AIDL文件最終生成對(duì)應(yīng)的.java文件,該文件里實(shí)現(xiàn)了客戶端調(diào)用transact(xx)以及調(diào)用服務(wù)端的onTransact(xx),通過(guò).java文件就能實(shí)現(xiàn)進(jìn)程間通信。
  • .java文件里將工作分為了兩部分:一是客戶端的邏輯封裝在Proxy里,而服務(wù)端的邏輯封裝在Stub里,典型的Proxy-Stub(代理-樁)模式。
  • 進(jìn)程間通信的核心是Binder,AIDL本身并不能實(shí)現(xiàn)進(jìn)程間通信,僅僅是簡(jiǎn)化了編碼的流程。

接下來(lái)將重點(diǎn)分析AIDL 傳遞自定義數(shù)據(jù)類型以及定向Tag相關(guān)問(wèn)題。

本文基于Android 10.0。

您若喜歡,請(qǐng)點(diǎn)贊、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中,和我一起步步為營(yíng)學(xué)習(xí)Android

1、Android各種Context的前世今生
2、Android DecorView 必知必會(huì)
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動(dòng)Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創(chuàng)建到View的顯示過(guò)
13、Android IPC 系列
14、Android 存儲(chǔ)系列
15、Java 并發(fā)系列不再疑惑
16、Java 線程池系列

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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