Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)

文章大纲

  • 引言
  • 一、AIDL概述
  • 二、AIDL 中核心的角色和方法
  • 三、通过AIDL 文件生成的接口文件解析
    • 1、aidl文件自动生成Java对应的aidl接口文件的通用结构
    • 2、android.os.IInterface和android.os.IBinder
    • 3、Stub
    • 4、Stub#asInterface
    • 5、Stub#onTransact方法
    • 6、Stub.Proxy
  • 四、AIDL 的使用的核心步骤及流程
    • 1、声明业务接口方法并创建aidl文件
    • 2、创建Parcelable实体类的aidl文件对应的Java类
    • 3、编译后自动生成接口和对应的实体类Java文件
    • 4、Server端继承aidl接口中对应的Stub抽象类实现具体的Binder对象
    • 5、创建Server端的Service并在manifest中声明
    • 6、Client 端拷贝Server端所有的aidl文件并在清单文件中声明
    • 7、创建具体的ServiceConnection
    • 8、Client端传入ServiceConnection实例到bindService 方法绑定Server进程服务
    • 9、通过Server端代理对象进行通信
    • 小结

引言

经过前面几篇文章相信,你应该已经基本掌握了AIDL的用法,但或许只知其然不知其所以然,AIDL 是如何一步步实现跨进程通信的,当然本质是通过Binder 机制(但并不意味着AIDL就是Binder),但是如何使用Binder呢?请注意这篇文章不会深入Binder,因为Binder 作为Android 的IPC 通信核心机制,要总结起来实在是太多太多知识点了,这篇文章主要是着重于AIDL的设计和实现原理,系列文章:

  • Android进阶——AIDL详解之使用远程服务AIDL实现进程间(IPC)简单通信小结(一)
  • Android进阶——AIDL详解之使用远程服务AIDL实现进程间带远程回调接口和自定义Bean的较复杂通信小结(二)
  • Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)

一、AIDL概述

Android接口定义语言AIDL (Android Interface Definition Language)定义了客户端与服务端均认可的编程接口规范,以便二者使用进程间通信 (IPC) 进行相互通信,AIDL文件会在编译时由SDK 工具生成对应的Java接口文件和实现类文件,然后通过回调方法返回一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法,AIDL的本质是系统提供了一套可快速实现Binder的工具。

官方建议:仅当需要在不同应用的客户端通过 IPC 方式访问服务(在服务中进行多线程处理)时才有必要使用 AIDL。若无需跨越不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;又或者只想执行 IPC,但不需要处理多线程时使用 Messenger 来实现接口。

二、AIDL 中核心的角色和方法

名称 说明
IBinder IBinder是一个接口,代表了跨进程通信的能力,Binder对象。
IInterface IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
AIDL接口 继承IInterface。
Binder Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
Stub类 Binder的实现类,负责接收Binder驱动的连接,服务端通过这个类来提供服务。
Proxy类 服务器的本地代理,负责应用层向底层通信,客户端通过这个类调用服务器的方法。
asInterface() 客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。如果客户端和服务端位于统一进程,则直接返回Stub对象本身,否则返回系统封装后的Stub.proxy对象
asBinder() 根据当前调用情况返回代理Proxy的Binder对象。
onTransact() 运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
transact() 运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。

三、通过AIDL 文件生成的接口文件解析

在定义好aidl文件之后,我们对项目进行编译,编译时SDK 工具会自动生成aidl文件对应的Java 接口文件,编译成功之后就可以在build 目录下看到对应的Java文件。

不同版本的AS 保存的具体路径有所不同,AS 3.4.x 是在xxx\build\generated\aidl_source_output_dir下。

以一个简单获取湿度的IGetWet.aidl为例:

package com.crazymo.remoteserver;
interface IGetWet {
    float getWet();
}

编译后会自动生成IGetWet.java 接口类文件:

// aidl文件存放的包名
package com.crazymo.remoteserver;

//IGetWet aidl文件的名称 继承IInterface
public interface IGetWet extends android.os.IInterface{
	
  /** Default implementation for IGetWet.  */
  public static class Default implements com.crazymo.remoteserver.IGetWet{
    
	@Override 
	public float getWet() throws android.os.RemoteException{
      return 0.0f;
    }
	
    @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.crazymo.remoteserver.IGetWet{
    private static final java.lang.String DESCRIPTOR = "com.crazymo.remoteserver.IGetWet";
    /** Construct the stub at attach it to the interface. */
    
	public Stub(){
	  //Stub 初始化时,将自己(实际上是将继承类)绑定到 Binder 的 interface 上
      this.attachInterface(this, DESCRIPTOR);
    }
	
    /**
     * Cast an IBinder object into an com.crazymo.remoteserver.IGetWet interface,
     * generating a proxy if needed.
     */
    public static com.crazymo.remoteserver.IGetWet asInterface(android.os.IBinder obj){
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.crazymo.remoteserver.IGetWet))) {
        return ((com.crazymo.remoteserver.IGetWet)iin);
      }
      return new com.crazymo.remoteserver.IGetWet.Stub.Proxy(obj);
    }
	
    @Override 
	public android.os.IBinder asBinder(){
      return this;
    }
	 /**
      * 根据code参数来处理,这里面会调用真正的业务实现类
      */
    @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_getWet:{
          data.enforceInterface(descriptor);
          float _result = this.getWet();
          reply.writeNoException();
          reply.writeFloat(_result);
          return true;
        }
		
        default:{
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
	
    private static class Proxy implements com.crazymo.remoteserver.IGetWet{
      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 float getWet() throws android.os.RemoteException{
	  	//如果有传入参数的话 最终还是序列化后写入_data,而底层响应是写到_reply
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        float _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getWet, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getWet();
          }
          _reply.readException();
          _result = _reply.readFloat();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.crazymo.remoteserver.IGetWet sDefaultImpl;
    }
	
    //类似方法的id作用
    static final int TRANSACTION_getWet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    
	public static boolean setDefaultImpl(com.crazymo.remoteserver.IGetWet impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.crazymo.remoteserver.IGetWet getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public float getWet() throws android.os.RemoteException;
}

接口文件结构如下
Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)_第1张图片

1、aidl文件自动生成Java对应的aidl接口文件的通用结构

说明:

  • aidl文件代表.aidl
  • aidl接口代表的是aidl文件生成的对应的Java接口;
  • Binder对象并非特指而是泛指Binder 类对象
  • package aidl文件对应的包名
  • 使用aidl文件的名称作为Java 接口的名称并继承android.os.IInterface
  • 自动生成静态内部类Stub(继承自IBinder并实现具体的aidl接口),接口中有两个重要的方法——Stub#asInterface和Stub#onTransact方法
  • Stub内部类以代理思想创建对应的Stub的内部代理类Proxy(实现具体的aidl接口)
  • Stub 类中还有一个重要的属性——DESRIPTOR(Binder的唯一标识,一般用当前Binder的全类名表示,因为Java的aidl接口继承了IInterface,所以这个最外层的aidl接口可以看成是一个Binder对象)
  • Stub和Stub.Proxy都实现了aidl接口,所以就把aidl文件中定义的方法引入过来了。

2、android.os.IInterface和android.os.IBinder

作为Framework 层提供给应用使用Binder的基础接口,所以如果你要自己去使用Binder通信的话,你的接口类也必须去继承这个接口。

如果你能跟到更深层次,发现它只是一种抽象设计,是为了让应用业务逻辑面向接口编程,而不用过度依赖实现。

简而言之,IInterface 代表的就是跨进程对象具备什么样的能力(定义了哪些通信接口方法),对应着aidl文件定义的方法,用于声明跨进程通信的业务接口方法

注意:对于通过aidl文件自动生成对应的aidl接口方式来说IInterface是必不可少的,而如果是自己手动实现则不是必要的。

Binder对象/代理对象在服务端和客户端是共享的,在客户端通过Binder对象获取实现了IInterface接口的对象来调用远程服务,Parcelable序列化后通过Binder来实现参数传递,服务端获取Binder对象并保存IInterface接口对象

public class Binder implement IBinder{
    void attachInterface(IInterface plus, String descriptor)
    IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来
    ...
}

Binder具有被跨进程传输的能力是因为它实现了IBinder接口,系统会为每个实现了该接口的对象提供跨进程传输,Binder具有的完成特定任务的能力是通过它的IInterface的对象获得的,attachInterface方法会将(descriptor,plus)作为(key,value)对存入Binder对象中的一个Map对象中,Binder对象可通过attachInterface方法持有一个IInterface对象(即plus)的引用,并依靠它获得完成特定任务的能力;而queryLocalInterface方法可以认为是根据key值(即参数 descriptor)查找相应的IInterface对象。

3、Stub

Stub 继承自Binder并实现了定义的aidl接口类,并作为是aidl接口中的一个内部抽象类。Server 进程需继承 Stub并实例化,同时作为Service#onBind方法返回值传递到客户端,用于初始化 IPC 环境以及负责与Binder驱动通信接收跨进程消息。

public Stub(){
	this.attachInterface(this, DESCRIPTOR);
}

在服务端进程里实现具体的Stub 时获得Binder对象,并调用attachInterface方法保存IInterface对象。

4、Stub#asInterface

Stub类中最核心的方法,将Server 端IBinder对象转换为对应的aidl接口实例(com.crazymo.remoteserver.IGetWet),Client端使用时通过Stub.asInterface(binder)转化为具体的接口类对象(代理对象)。若Server和Client端都位于同一个进程,则直接返回Server端的Stub对象本身,否则返回Stub.Proxy代理对象。无论是哪个进程,拿到反序列化的 IBinder 以后,通过这个静态方法来获取。 如果是 Server 进程(local),直接从 binder 中直接取出之前 attach的 IInterface 实例,那么调用 aidl接口的方法就相当于直接调用Stub 实例的方法了;而如果是 Client 进程,IBinder 只是系统在远程创建的一个 Proxy 类并无实现,因此,iin 将变成 null,此时 asInterface 会创建一个Stub.Proxy 代理类并实现 aidl接口。

 public static com.crazymo.remoteserver.IGetWet asInterface(android.os.IBinder obj){
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.crazymo.remoteserver.IGetWet))) {
        return ((com.crazymo.remoteserver.IGetWet)iin);
      }
      return new com.crazymo.remoteserver.IGetWet.Stub.Proxy(obj);
    }

传入DESCRIPTOR作为key并调用queryLocalInterface(DESCRIPTOR)方法,查找到对应的IInterface对象,然后判断对象的类型,如果是同一个进程调用则返回IBookManager对象,由于是跨进程调用则返回Proxy对象,即Binder类的代理对象。

5、Stub#onTransact方法

方法运行在服务端中的Binder线程池中,服务端通过code确定客户端所请求的目标方法是什么,从data中取出目标方法所需的参数,然后执行目标方法,并将目标方法的返回值写入reply。如果该方法的返回false,则表示客户端的请求回失败,可以利用这个特性来做权限验证。

6、Stub.Proxy

客户端真正使用的对象,Stub.Proxy实现aidl接口,将方法的标识,参数和返回值对象传入方法mRemote.transact中,发起远程调用请求,同时挂起当前线程,然后服务端的Stub#onTransact被调用,当onTransact返回true,调用成功,从reply对象中取出返回值,返回给客户端调用方,当Client 进程使用时通过 Stub.asInterface 创建,同时会将方法调用的数据,都通过 mRemote 转发给Server端的 Binder 实例。

四、AIDL 的使用的核心步骤及流程

Binder 基于C/S 架构,AIDL是快速实现Binder的一种机制。Client 可以向远程Server 发起一个附带数据的交易 (transact)并可以接收到响应。其中Stub 类(Binder对象/代理)在 Server 进程中实例化,并通过 bindService方式(通过Service.onBind回调)或者其他跨进程通讯方式,将 IBinder 接口传递给 Client,并通过 Service.onBind 方法传递给 Client 进程。
Client 进程接收到 IBinder 以后,通过 Stub.asInterface 方法转换成对应的aidl接口实例
,Client 持有反序列化出的 IBinder实例(并不是Binder而是BinderProxy),然后借助这个IBinder实例与Server进行交易(通信)。当Stub.asInterface 在本地进程工作时,则返回 Stub 实例。反之,创建一个 Proxy 实例来代理通讯。

1、声明业务接口方法并创建aidl文件

除了包含普通的接口方法,还可以根据业务创建对应用于声明的Parcelable实体类的aidl文件。如果自定义的Parcelable对象,还必须创建一个和它同名的AIDL文件,并在其中声明它为具体的Parcelable类型

2、创建Parcelable实体类的aidl文件对应的Java类

主要就是实现Parcelable接口并重写writeToParcel方法等。

3、编译后自动生成接口和对应的实体类Java文件

4、Server端继承aidl接口中对应的Stub抽象类实现具体的Binder对象

创建一个Service用来监听客户端的连接请求。然后在Service中实现Stub 抽象类(继承自aidl接口中),并实现业务方法的具体逻辑,当客户端连接服务端,会自动回调Service的onBind回调方法,并把Binder对象返回到客户端。

5、创建Server端的Service并在manifest中声明

继承Service 重写onBind方法,且在onBind中返回具体的Binder对象(第四步的实例)

6、Client 端拷贝Server端所有的aidl文件并在清单文件中声明

  • 引入Server端的AIDL即把服Server端AIDL全部文件复制到客户端的aidl目录下
  • 引入Server端定义的Parcelable,把Server端定义的实现了Parcelable接口的JavaBean 复制到客户端相同的路径下

首先将服务端工程中的aidl文件夹下的内容(一模一样的路径和结构)整个拷贝到客户端工程的对应位置下,若在一个应用中,就不需要拷贝了,其他情况一定不要忘记这一步。

7、创建具体的ServiceConnection

在ServiceConnection#onServiceConnected回调中通过Stub.asInterface方法把序列化的Binder对象转为具体的aidl接口实例。

private ServiceConnection conn= new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            //通过服务端onBind方法返回的binder对象得到aidl接口的实例,得到实例就可以调用它的方法了
            mRemoteApi = IBookManager.Stub.asInterface(binder);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteApi = null;
        }
    };

8、Client端传入ServiceConnection实例到bindService 方法绑定Server进程服务

Client通过bindService来Server端对应的Binder对象/代理,并在ServiceConnection的onServiceConnected回调中获取真正的aidl接口实例。具体地在Activity中通过bindService()方法绑定远程服务,本质上是通过ContextImpl的bindService()方法。

@Override
public boolean bindService(Intent service, ServiceConnection conn,
        int flags) {
    warnIfCallingFromSystemProcess();
    return bindServiceCommon(service, conn, flags, mMainThread.getHandler(),
            Process.myUserHandle());
}

ContextImpl#bindServiceCommon

1597    private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
1598            handler, UserHandle user) {
1599        // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
1600        IServiceConnection sd; 
1601        if (conn == null) {
1602            throw new IllegalArgumentException("connection is null");
1603        }
1604        if (mPackageInfo != null) {
1605            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); 
//返回ServiceConnection对应的binder对象,这个在客户端也有保存,避免一个客户端重复生成binder对象(首次创建,本地保存起来)
1606        } else {
1607            throw new RuntimeException("Not supported in system context");
1608        }
1609        validateServiceIntent(service);
1610        try {
1611            IBinder token = getActivityToken();
1612            if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null
1613                    && mPackageInfo.getApplicationInfo().targetSdkVersion
1614                    < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1615                flags |= BIND_WAIVE_PRIORITY;
1616            }
1617            service.prepareToLeaveProcess(this);
1618            int res = ActivityManager.getService().bindService(
1619                mMainThread.getApplicationThread(), getActivityToken(), service,
1620                service.resolveTypeIfNeeded(getContentResolver()),
1621                sd, flags, getOpPackageName(), user.getIdentifier());
1622            if (res < 0) {
1623                throw new SecurityException(
1624                        "Not allowed to bind to service " + service);
1625            }
1626            return res != 0;
1627        } catch (RemoteException e) {
1628            throw e.rethrowFromSystemServer();
1629        }
1630    }

一路深追下去,还是会经过AMS 通过Binder 机制完成通绑定,绑定成功后会回调onBinder()方法,远程服务绑定成功后,会回调onServiceConnected()方法,通知当前Client进程,远程服务已经连接成功,可以获取远程服务的代理进行进程间的通信了。

Service的bindService 由于涉及到AMS、还有ActivityService等众多核心知识点,篇幅问题不便展开。

Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)_第2张图片

9、通过Server端代理对象进行通信

获得了Binder类的代理对象,并且通过代理对象获得了IInterface对象,那么就可以调用接口的具体实现方法了,来实现调用服务端方法的目的。
Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)_第3张图片

      @Override 
	  public float getWet() throws android.os.RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        float _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
		  /**
		  *第一个参数,识别调用哪一个方法的ID
		  *第二个参数,序列化后的参数(序列化后就是2 进制byte字符串)
		  *第三个参数,调用方法后返回的数据
		  */
          boolean _status = mRemote.transact(Stub.TRANSACTION_getWet, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getWet();
          }
          _reply.readException();
          _result = _reply.readFloat();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

Proxy对象中的transact调用发生后,系统意识到Proxy对象想找它的真身Binder对象(系统其实一直存着Binder和Proxy的对应关系)。于是系统将这个请求中的数据转发给Binder对象,Binder对象将会在onTransact中收到Proxy对象传来的数据,于是它从data中取出客户端进程传来的数据,又根据第一个参数确定想让它执行具体操作,Binder线程中onTransact被触发,于是它就执行了响应操作并把结果写回reply。

transactNative()方法最后会调用远程服务Stub的onTransact()方法,而onTransact()方法会通过asBinder()方法获取远程服务实例并调用对应的真正的实现方法。

        case TRANSACTION_getWet:
        {
          data.enforceInterface(descriptor);
          //调用服务端对象实例去真正完成具体的操作
          float _result = this.getWet();
          reply.writeNoException();
          reply.writeFloat(_result);
          return true;
        }

最后在transact方法获得_reply并返回结果,客户端线程被唤醒。因此调用服务端方法做耗时操作时,应开启子线程,防止超时导致ANR。
Android 进阶——AIDL 详解之AIDL 借助Binder 实现IPC背后的核心思想和原理(三)_第4张图片

  • 获取Stub获取远程服务的代理Stub.Proxy。

  • 通过代理Stub.Proxy调用远程服务的方法,代理Stub.Proxy通过asBinder()方法获取BinderProxy调用transact()方法通知远程服务,而transact()方法使用JNI技术调用了native方法transactNative()方法。

  • transactNative()方法最后会调用远程服务中Stub的onTransact()方法,而onTransact()方法会通过asBinder()方法获取远程服务实例并调用对应的方法。

小结

AIDL 并不等于Binder,也不并不是Android Developer使用Binder的唯一方式,AIDL 文件只是帮我们把transact()方法的调用给隐藏起来,AIDL声明的每一个方法最终都还是会通过这个transact()方法与另一个进程的代理对象进行通信。

未完待续…

你可能感兴趣的:(Android,进阶,android,AIDL,Binder,Stub)