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零基础入门到精通,高手进阶之路

相关推荐
Dingdangr4 小时前
Android中的Intent的作用
android
技术无疆4 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP4 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy11 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯11 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h14 小时前
Android中的单例模式
android·单例模式
计算机源码社18 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽19 小时前
重修设计模式-结构型-门面模式
android
晨春计20 小时前
【git】
android·linux·git
标标大人21 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言