由浅入深 学习 Android Binder(一)- AIDL

【Android】 专栏收录该内容
201 篇文章 2 订阅

Android Binder系列文章:
由浅入深 学习 Android Binder(一)- AIDL
由浅入深 学习 Android Binder(二)- bindService流程
由浅入深 学习 Android Binder(三)- java binder深究(从java到native)
由浅入深 学习 Android Binder(四)- ibinderForJavaObject 与 javaObjectForIBinder
由浅入深 学习 Android Binder(五)- binder如何在进程间传递
由浅入深 学习 Android Binder(六)- IPC 调用流程
由浅入深 学习 Android Binder(七)- IServiceManager与ServiceManagerNative(java层)
由浅入深 学习 Android Binder(八)- IServiceManager与BpServiceManager(native层)
由浅入深 学习 Android Binder(九)- service_manager 与 svclist
由浅入深 学习 Android Binder(十)- 总结
由浅入深 学习 Android Binder(十一) binder线程池

概述

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

为了便于读者理解,本文不会探究Binder的实现细节,可以认为Binder在此文的分析中被看做是一个“黑盒”。

有一定经验的读者可以直接到文末看总结,最终流程图如下:
在这里插入图片描述

Demo

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

  • MainActivity.java
    自定义ServiceConnection,然后将ServiceConnection传入bindService,获取到IBinder后实现远程调用。
  • RemoteTestService.java
    在ITestServer.Stub中实现需要远程调用的方法testFunction(),在onBind中返回。
  • IServer.aidl
    定义一个aidl文件和需要远程调用的方法
  • AndroidManifest.xml
    在此设置RemoteTestService在remote进程。

MainActivity.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

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

interface IServer {
    int testFunction(String s);
}

AndroidManifest.xml

<?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自动生成的代码。

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文件本身并不会参与代码的运行,编译后自动生成的文件才是关键,因此这个文件由开发者来手动实现也是完全可以的。

自动生成的接口文件,有较多代码在demo的IPC中并没有使用。
因此笔者打算用源码解析就用自己写的接口文件,也是减少给读者的混淆项。
笔者自己实现的 接口ITestServer.java,源码如下:

public interface ITestServer extends android.os.IInterface {

  public int testFunction(java.lang.String s) throws android.os.RemoteException;

  public static abstract class Stub extends android.os.Binder
      implements com.example.bindservicetest.ITestServer {
    //binder唯一标识
    private static final java.lang.String DESCRIPTOR = "com.example.bindservicetest.ITestServer";
    //testFunction的调用id
    static final int TRANSACTION_testFunction = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    public Stub() {
      //将interface提供出去,这样当统一进程其他位置执行IBinder.queryLocalInterface的时候就可以获取到这个Binder
      this.attachInterface(this, DESCRIPTOR);
    }

    public static com.example.bindservicetest.ITestServer asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//查找当前进程是否有这个binder
      if (((iin != null) && (iin instanceof com.example.bindservicetest.IServer))) {
        return ((com.example.bindservicetest.ITestServer) iin);
      }
      return new com.example.bindservicetest.ITestServer.Stub.Proxy(obj);
    }

    @Override public android.os.IBinder asBinder() {
      return this;//继承自IInterface,返回当前Stub的binder对象
    }

    @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);//需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
          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.ITestServer {
      private final android.os.IBinder mRemote;

      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }

      @Override public android.os.IBinder asBinder() {
        return mRemote;//继承自IInterface,返回当前Proxy的binder对象
      }

      @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);//需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
          _data.writeString(s);//写入参数
          boolean _status =
              mRemote.transact(Stub.TRANSACTION_testFunction, _data, _reply, 0);//通过transact来IPC调用
          _reply.readException();
          _result = _reply.readInt();//读取运行结果
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }
  }
}

ITestServer 源码解析

通过android studio,可以看到该类的结构如下。

接下来我们就主要分析三个类:

  • ITestServer
    Stub和Proxy都implement了它。
    java上层可以使用这个接口来保留Binder的对象。在使用aidl的时候,完全可以只使用这个对象而不用关心这个对象是Stub还是Proxy。
  • ITestServer.Stub
    当前进程的Binder对象。
    在onTransact方法中实现了对IPC方法的逻辑,Proxy调用的时候会触发。
  • ITestServer.Stub.Proxy
    当前进程通过Proxy实现了对远端方法的调用。
    当前进程调用远端进程方法,实际上是调用了远端Binder的transact方法,最终执行到Stub中的onTransact。

ITestServer

public interface ITestServer extends android.os.IInterface {

  public int testFunction(java.lang.String s) throws android.os.RemoteException;

----------------Stub的代码先不看
}
/**
 * Base class for Binder interfaces.  When defining a new interface,
 * you must derive it from IInterface.
 */
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

要点如下:

  • ITestServer本身只包括“需要IPC的方法”,此处即int testFunction(String)
  • 这个类继承了IInterface,需要实现asBinder接口。
    asBinder()会返回正确的Binder对象。如果是同进程调用就会直接返回当前进程的Binder,如果是IPC,就会返回远程调用的进程的Binder。
    对应源码就是,Stub的asBinder()返回的是this,而Proxy返回的是mRemote.

ITestServer.Stub

DESCRIPTOR

//binder唯一标识
    private static final java.lang.String DESCRIPTOR = "com.example.bindservicetest.ITestServer";

是Binder的唯一标识。
不同的进程之间,通过序列化传递DESCRIPTOR来找到对应的Binder。
相同进程,也需要通过DESCRIPTOR才找到对应的Binder。

TRANSACTION_testFunction

//testFunction的调用id
    static final int TRANSACTION_testFunction = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

调用IPC方法的唯一id。
Stub.onTransact()中会通过TRANSACTION_testFunction来对应 执行testFunction()方法的逻辑。
Proxy调用transact的时候,也是通过传递TRANSACTION_testFunction,来标识自己想要执行的逻辑。

Stub初始化

    public Stub() {
      //将interface提供出去,这样当统一进程其他位置执行IBinder.queryLocalInterface的时候就可以获取到这个Binder
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Convenience method for associating a specific interface with the Binder.
     * After calling, queryLocalInterface() will be implemented for you
     * to return the given owner IInterface when the corresponding
     * descriptor is requested.
     */
    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    
        /**
     * Use information supplied to attachInterface() to return the
     * associated IInterface if it matches the requested
     * descriptor.
     */
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

初始化时,调用attachInterface()。
attachInterface()之后,相同进程调用queryLocalInterface()的时候就能找到这个Binder。
在下面asInterface()中,就用到了queryLocalInterface()。

asInterface()

    public static com.example.bindservicetest.ITestServer asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//查找当前进程是否有这个binder
      if (((iin != null) && (iin instanceof com.example.bindservicetest.IServer))) {
        return ((com.example.bindservicetest.ITestServer) iin);
      }
      return new com.example.bindservicetest.ITestServer.Stub.Proxy(obj);
    }

用于将服务端的IBinder对象转化成ITestServer对象。
如果是server和client进程相同,那么会直接通过queryLocalInterface(DESCRIPTOR)找到Binder返回,这里返回的其实就是Stub对象。
如果server和client在不同进程,那么会返回一个封装后的Proxy对象。

asBinder()

    @Override public android.os.IBinder asBinder() {
      return this;//继承自IInterface,返回当前Stub的binder对象
    }

返回Stub自己。
在Proxy中返回的是远端进程的Binder。

onTransact()

    @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);//需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
          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);
        }
      }
    }

实现了被IPC调用后的逻辑。
一个方法的调用在onTransact中一般有以下几个逻辑:

  1. 从data中读出参数
  2. 将读出的参数带入server的方法中,得到执行结果。
  3. 将执行结果写入reply

参数和结果为什么读写都需要通过Parcel?
进程间通信由于资源不共享,因此无法直接传递对象,只能通过序列化在不同的空间拷贝两份相同的资源。
而Parcel就是序列化的一种方案。

ITestServer.Stub.Proxy

    private static class Proxy implements com.example.bindservicetest.ITestServer {
      private final android.os.IBinder mRemote;

      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }

      @Override public android.os.IBinder asBinder() {
        return mRemote;//继承自IInterface,返回当前Proxy的binder对象
      }

      @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);//需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
          _data.writeString(s);//写入参数
          boolean _status =
              mRemote.transact(Stub.TRANSACTION_testFunction, _data, _reply, 0);//通过transact来IPC调用
          _reply.readException();
          _result = _reply.readInt();//读取运行结果
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }

Proxy初始化

      private final android.os.IBinder mRemote;

      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }

Proxy在初始化的时候会引用server的IBinder。

asBinder()

      @Override public android.os.IBinder asBinder() {
        return mRemote;//继承自IInterface,返回当前Proxy的binder对象
      }

Proxy在asBinder的时候会返回server的IBinder。
Stub在这个方法中会返回this。

testFunction()

      @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);//需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
          _data.writeString(s);//写入参数
          boolean _status =
              mRemote.transact(Stub.TRANSACTION_testFunction, _data, _reply, 0);//通过transact来IPC调用
          _reply.readException();
          _result = _reply.readInt();//读取运行结果
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

Proxy调用testFunction,实际上是通过调用mRemote.transact()来触发远端Stub的onTransact()。
一般调用的步骤如下:

  1. 创建参数与返回值的Parcel对象,将参数写入Parcel。
  2. 调用mRemote.transact(),返回值会写入到Parcel对象中。
  3. 从Parcel对象中读出返回值并return。

总结

根据上文的分析,aidl自动生成的文件的源码大概可以总结成下图:

在这里插入图片描述

基本步骤如下:

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

继续探索

读完本文,细心的读者会发现本文还留有两个较大的问题完全没有提及:

  1. ServiceConnection是如何获取到Binder的
  2. 通过ServiceConnection获取到的Binder是什么,或者说java层的Binder是什么

这两点笔者后面的文章会继续探索,有兴趣的读者可以自行探索或者欢迎持续关注。

  • 5
    点赞
  • 6
    评论
  • 27
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值