Android 应用工程师的 Binder 原理剖析(四)AIDL

AIDL可以生成C++ 文件,哈哈,不仅仅是可以生成java文件哦,当然,要在android的比较新的版本上面才能够实现这点,目前需要在api-29之后。

概述

aidl是常用的android IPC方式,本文将根据一个demo来解析下AIDL的原理。

为了便于读者理解,本文不会探究Binder的实现细节,可以认为Binder在此文的分析中被看做是一个"黑盒"。有一定经验的读者可以直接到文末看总结,最终流程图如下:

Demo讲解一下AIDL

demo实现内容:MainActivity通过AIDL实现IPC来调用另一个进程中RemoteTestService的一个方法。主要有以下几个文件:

  1. MainActivity.java

自定义ServiceConnection,然后将ServiceConnection传入bindService,获取到IBinder后实现远程调用。

  1. RemoteTestService.java在ITestServer.Stub中实现需要远程调用的方法testFunction(),在onBind中返回。
  2. IServer.aidl定义一个aidl文件和需要远程调用的方法
  3. AndroidManifest.xml在此设置RemoteTestService在remote进程。

先看MainActivity.java

java 复制代码
public class MainActivity extends AppCompatActivity {

 private IServer iServer;
 private ServiceConnection serviceConnection = new ServiceConnection() {
   @Override
   public void onServiceConnected(ComponentName name, IBinder service) {
     iServer = IServer.Stub.asInterface(service);
     System.out.println("onServiceConnected");

     try {
       int a = iServer.testFunction("test string");
       Log.i("test", "after testFunction a:" + a);
    } catch (Exception e) {
       e.printStackTrace();
    }
  }

   @Override
   public void onServiceDisconnected(ComponentName name) {
     Log.i("test", "onServiceDisconnected");
  }
};

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

   Intent intent = new Intent(MainActivity.this, RemoteTestService.class);
   bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}

接下来看RemoteTestService.java

scala 复制代码
public class RemoteTestService extends Service {

 private IServer.Stub serverBinder = new IServer.Stub() {
   @Override public int testFunction(String s) throws RemoteException {
     Log.i("test","testFunction s= " + s);
     return 0;
  }
};

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

再看IServer.aidl

arduino 复制代码
interface IServer {
   int testFunction(String s);
}

再看 AndroidManifest.xml

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.bindservicetest">

 <application
     android:allowBackup="true"
     android:icon="@mipmap/ic_launcher"
     android:label="@string/app_name"
     android:roundIcon="@mipmap/ic_launcher_round"
     android:supportsRtl="true"
     android:theme="@style/AppTheme">
   <activity android:name=".MainActivity">
     <intent-filter>
       <action android:name="android.intent.action.MAIN" />

       <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
   </activity>

   <service
       android:name=".RemoteTestService"
       android:process=":remote" />
 </application>
</manifest>

aidl自动生成的文件定义完aidl文件,编译会自动生成一个java接口文件。这个接口文件在build目录下,具体路径如下:

打开文件,我们就可以看到aidl自动生成的代码。

java 复制代码
public interface IServer extends android.os.IInterface
{
 /** Default implementation for IServer. */
 public static class Default implements com.example.bindservicetest.IServer
{
   @Override public int testFunction(java.lang.String s) throws android.os.RemoteException
  {
     return 0;
  }
   @Override
   public android.os.IBinder asBinder() {
     return null;
  }
}
 /** Local-side IPC implementation stub class. */
 public static abstract class Stub extends android.os.Binder implements com.example.bindservicetest.IServer
{
   private static final java.lang.String DESCRIPTOR = "com.example.bindservicetest.IServer";
   /** Construct the stub at attach it to the interface. */
   public Stub()
  {
     this.attachInterface(this, DESCRIPTOR);
  }
   /**
    * Cast an IBinder object into an com.example.bindservicetest.IServer interface,
    * generating a proxy if needed.
    */
   public static com.example.bindservicetest.IServer asInterface(android.os.IBinder obj)
  {
     if ((obj==null)) {
       return null;
    }
     android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
     if (((iin!=null)&&(iin instanceof com.example.bindservicetest.IServer))) {
       return ((com.example.bindservicetest.IServer)iin);
    }
     return new com.example.bindservicetest.IServer.Stub.Proxy(obj);
  }
   @Override public android.os.IBinder asBinder()
  {
     return this;
  }
   @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)
    {
       case INTERFACE_TRANSACTION:
      {
         reply.writeString(descriptor);
         return true;
      }
       case TRANSACTION_testFunction:
      {
         data.enforceInterface(descriptor);
         java.lang.String _arg0;
         _arg0 = data.readString();
         int _result = this.testFunction(_arg0);
         reply.writeNoException();
         reply.writeInt(_result);
         return true;
      }
       default:
      {
         return super.onTransact(code, data, reply, flags);
      }
    }
  }
   private static class Proxy implements com.example.bindservicetest.IServer
  {
     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 testFunction(java.lang.String s) 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(s);
         boolean _status = mRemote.transact(Stub.TRANSACTION_testFunction, _data,
                                            _reply, 0);
         if (!_status && getDefaultImpl() != null) {
           return getDefaultImpl().testFunction(s);
        }
         _reply.readException();
         _result = _reply.readInt();
      }
       finally {
         _reply.recycle();
         _data.recycle();
      }
       return _result;
    }
     public static com.example.bindservicetest.IServer sDefaultImpl;
  }
   static final int TRANSACTION_testFunction =
  (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
   public static boolean setDefaultImpl(com.example.bindservicetest.IServer impl) {
     if (Stub.Proxy.sDefaultImpl == null && impl != null) {
       Stub.Proxy.sDefaultImpl = impl;
       return true;
    }
     return false;
  }
   public static com.example.bindservicetest.IServer getDefaultImpl() {
     return Stub.Proxy.sDefaultImpl;
  }
}
 public int testFunction(java.lang.String s) throws android.os.RemoteException;
}

上面的代码有些乱七八糟的,毕竟是aidl工具生成的,大家看核心部分就好了,那些try,那些if else 大可不必关注。所以核心代码就在下面:

Stub 里面的关键函数1-asInterface:

typescript 复制代码
public static com.example.testservice.IServer asInterface(android.os.IBinder obj)
{
   if ((obj==null)) {
  return null;
  }
   android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
   if (((iin!=null)&&(iin instanceof com.example.testservice.IServer))) {
  return ((com.example.testservice.IServer)iin);
  }
   return new com.example.testservice.IServer.Stub.Proxy(obj);//核心代码 1
}

该核心代码就是创建一个Proxy对象,同时将IBinder对象的obj传值给Proxy。

Stub类中的关键函数2-onTransact():

java 复制代码
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)
    {
       case INTERFACE_TRANSACTION:
      {
         reply.writeString(descriptor);
         return true;
      }
       case TRANSACTION_testFunction:
      {
         data.enforceInterface(descriptor);
         java.lang.String _arg0;
         _arg0 = data.readString();
         int _result = this.testFunction(_arg0); //核心代码2
         reply.writeNoException();
         reply.writeInt(_result);
         return true;
      }
       default:
      {
         return super.onTransact(code, data, reply, flags);
      }
    }
  }

onTransact函数功能主要是接收来自binder底层的调用,通过binder底层调用onTransact函数,将函数执行到核心代码2,具体的流程请看后面的小结。

Prox类里面的关键函数1 testFunction:

ini 复制代码
public int testFunction(java.lang.String s) 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(s);
         boolean _status = mRemote.transact(Stub.TRANSACTION_testFunction, _data,
                           _reply, 0); // 核心代码3
         if (!_status && getDefaultImpl() != null) {
           return getDefaultImpl().testFunction(s);
        }
         _reply.readException();
         _result = _reply.readInt();
      }
       finally {
         _reply.recycle();
         _data.recycle();
      }
       return _result;
    }

这个函数是和aidl文件里面定义的函数名字是一样的,后面进行跨进程通信会引用到这个函数,这个的功能是创建一个Parcel 数据_data,为什么必须是Parcel数据了,因为android跨进程传递的数据必须序列化,而序列化所采用的方式就是Parcel。大家如果研究过Parcel序列化,你们会发现它的主要实现是在native层,而Binder通信最主要的部分是在native层。另外,如果跨进程的调用testFunction有返回值,那么这个返回值讲以 _replay来存储和传递。

类的结构图如下:

以上是aidl生成的java代码的介绍,其实在android api 29之后,aidl可以生成C++的代码,具体的生成的代码的逻辑和java代码的逻辑一模一样,只是语言有修改而已。

小结

在运用AIDL通信的过程中,首先Client端(某个acitivty)先会发起bindService的请求,此时Server端(某service)会将自己的binder给Client端,也就是如下代码:

首先,Activity会执行下面的代码:

scss 复制代码
Intent intent = new Intent(MainActivity.this, RemoteTestService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);//核心代码

bindServcie会去绑定服务"RemoteTestService",在执行bindService的时候会回调到connection中去,而connection的代码如下:

typescript 复制代码
private ServiceConnection connection = new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           myService =  IServer.Stub.asInterface(service);//proxy
           Log.d(TAG, "onServiceConnected: ");
      }

       @Override
       public void onServiceDisconnected(ComponentName name) {
//           unbindService(connection);
           Log.d(TAG, "onServiceDisconnected: ");
      }
  };

当绑定成功就会执行onServiceConnected 回调函数,回调函数就会有一个IBinder对象service,此时通过asInterface函数,会将这个IBinder对象(service)转换成为一个Proxy对象。所以,我们Client去进行跨进程函数调用的时候就是使用这个proxy对象进行。

总结

binder基于AIDL的通信流程图如下:

Client端:MainActivity;

Server端:Service;

Service的对外aidl接口如上面的案例所示,

MainActivity已经通过BindService拿到了Service的IBinder对象。

aidl通信的基本步骤如下:

  1. Client通过ServiceConnection获取到Server的Binder,并且封装成一个Proxy。
  2. 通过Proxy来同步调用IPC方法(testFunction),同时通过Parcel将参数传给Binder,最终触发Binder的transact方法。
  3. Binder的transact方法最终会触发到Server上Stub的onTransact方法。
  4. Server上Stub的onTransact方法中,会先从Parcel中解析中参数,然后将参数带入真正的方法中执行,然后将结果写入Parcel后传回。
  5. 请注意:Client的Ipc方法中,执行Binder的transact时,是阻塞等待的,一直到Server逻辑执行结束后才会继续执行。当然,如果IPC方法是oneWay的方式,那么就是非阻塞的等待。
  6. 当Server返回结果后,Client从Parcel中取出返回值,于是实现了一次IPC调用。

所以,aidl 文件会生成一个java文件,这个java文件的意义在于将核心的binder驱动封装成为java层可以直接调用的代码,同时也处理了将java层的数据格式转换为Parcel格式数据进行跨进程传递的一个功能。所以,aidl是一个使用binder的标准方案,该方案的代码同样的可以通过用户自己编写的方式完成。

今日分享到此结束,对你有帮助的话,点个赞再走呗,如遇侵权联系删除
关注公众号:Android老皮

解锁 《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路

相关推荐
大白要努力!2 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟2 小时前
Android音频采集
android·音视频
小白也想学C4 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程4 小时前
初级数据结构——树
android·java·数据结构
闲暇部落6 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX8 小时前
Android 分区相关介绍
android
大白要努力!9 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee9 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood9 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-12 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记