谈谈Android Binder机制及AIDL使用

时间:2022-10-04 17:48:30

Binder原理

1、概述

Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC机制。例如当进 程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。不仅于 此,整个Android系统架构中,大量采用了Binder机制作为IPC(进程间通信, Interprocess Communication)方案。

当然也存在部分其他的IPC方式,如管道、SystemV、Socket等。那么Android为什 么不使用这些原有的技术,而是要使开发一种新的叫Binder的进程间通信机制呢?

顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

为什么要使用Binder?
性能方面 在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信 机制的性能有严格的要求,Binder相对于传统的Socket方式,更加高效。Binder数 据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内 存拷贝都不需要,但实现方式又比较复杂。

安全方面
传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信 的IP地址是客户端手动填入,很容易进行伪造。然而,Binder机制从协议本身就支 持对通信双方做身份校检,从而大大提升了安全性。

2、 Binder

IPC原理
从进程角度来看IPC(Interprocess Communication)机制
谈谈Android Binder机制及AIDL使用
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。例如,对应一 个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间。当然内核空间的 大小是可以通过参数配置调整的。对于用户空间,不同进程之间是不能共享的,而 内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享 的内核内存空间来完成底层通信工作的。Client端与Server端进程往往采用ioctl等方 法与内核空间的驱动进行交互。

Binder原理
Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager 以及Binder驱动,其中ServiceManager用于管理系统中的各种服务。架构图如下所 示:
谈谈Android Binder机制及AIDL使用
Binder通信的四个角色

Client进程: 使用服务的进程。
Server进程: 提供服务的进程。
ServiceManager进程: ServiceManager的作用是将字符形式的Binder名字转化成 Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder 实体的引用。
Binder驱动: 驱动负责进程之间Binder通信的建立,Binder在进程之间的传递, Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

Binder运行机制
图中Client/Server/ServiceManage之间的相互通信都是基于Binder机制。既然基于 Binder机制通信,那么同样也是C/S架构,则图中的3大步骤都有相应的Client端与 Server端。

注册服务(addService): Server进程要先注册Service到ServiceManager。该过 程:Server是客户端,ServiceManager是服务端。
获取服务(getService): Client进程使用某个Service前,须先向ServiceManager中 获取相应的Service。该过程:Client是客户端,ServiceManager是服务端。
使用服务: Client根据得到的Service信息建立与Service所在的Server进程通信的通 路,然后就可以直接与Service交互。该过程:Client是客户端,Server是服务端。

图中的ClientServerService Manager之间交互都是虚线表示,是由于它们彼此 之间不是直接交互的,而是都通过与Binder驱动进行交互的,从而实现IPC通信 (Interprocess Communication)方式。其中Binder驱动位于内核空间,ClientServerService Manager位于用户空间。Binder驱动和Service Manager可以看做 是Android平台的基础架构,而Client和Server是Android的应用层,开发人员只需自 定义实现Client、Server端,借助Android的基本平台架构便可以直接进行IPC通 信。

Binder运行的实例解释
首先我们看看我们的程序跨进程调用系统服务的简单示例,实现浮动窗口部分代 码:

  //获取WindowManager服务引用 
  WindowManager wm = (WindowManager) getSystemService(getApplicati on().WINDOW_SERVICE); 
  //布局参数layoutParams相关设置略... 
  View view = LayoutInflater.from(getApplication()).inflate(R.layo ut.float_layout, null); 
  //添加view 
  wm.addView(view, layoutParams);

注册服务(addService): 在Android开机启动过程中,Android会初始化系统的各种 Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。 这一步是系统自动完成的。
获取服务(getService): 客户端想要得到具体的Service直接向ServiceManager要 即可。客户端首先向ServiceManager查询得到具体的Service引用,通常是Service 引用的代理对象,对数据进行一些处理操作。即第2行代码中,得到的wm是 WindowManager对象的引用。
使用服务: 通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即 第6行调用WindowManageraddView函数,将触发远程调用,调用的是运行在 systemServer进程中的WindowManageraddView函数。
使用服务的具体执行过程
谈谈Android Binder机制及AIDL使用

  1. Client通过获得一个Server的代理接口,对Server进行调用。
  2. 代理接口中定义的方法与Server中定义的方法是一一对应的。
  3. Client调用某个代理接口中的方法时,代理接口的方法会将Client传递的参数打 包成Parcel对象。
  4. 代理接口将Parcel发送给内核中的Binder Driver。
  5. Server会读取Binder Driver中的请求数据,如果是发送给自己的,解包Parcel 对象,处理并将结果返回。
  6. 整个的调用过程是一个同步过程,在Server处理的时候,Client会Block住。因 此Client调用过程不应在主线程。

AIDL的使用

1.AIDL的简介

AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以 在Android设备上两个进程之间进行进程间通信(Interprocess Communication, IPC) 的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service) 对象的操作,就可以使用AIDL生成可序列化的参数,来完成进程间通信。

简言之,AIDL能够实现进程间通信,其内部是通过Binder机制来实现的,后面会 具体介绍,现在先介绍AIDL的使用。

2.AIDL的具体使用

AIDL的实现一共分为三部分,一部分是客户端,调用远程服务。一部分是服务端, 提供服务。最后一部分,也是最关键的是AIDL接口,用来传递的参数,提供进程间 通信。
先在服务端创建AIDL部分代码。
AIDL文件 通过如下方式新建一个AIDL文件
谈谈Android Binder机制及AIDL使用
默认生成格式

  interface IBookManager { 
      /**
       * Demonstrates some basic types that you can use as paramet ers 
       * and return values in AIDL. 
       */ 
     void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString);
   }

默认如下格式,由于本例要操作Book类,实现两个方法,添加书本和返回书本列 表。
定义一个Book类,实现Parcelable接口。

  public class Book implements Parcelable { 
     public int bookId; 
     public String bookName; 

     public Book() { 
     }

     public Book(int bookId, String bookName) { 
        this.bookId = bookId; 
        this.bookName = bookName; 
     }

     public int getBookId() { 
        return bookId; 
     }

     public void setBookId(int bookId) { 
        this.bookId = bookId; 
     }
     public String getBookName() { 
        return bookName; 
     }
     public void setBookName(String bookName) { 
        this.bookName = bookName; 
     }

     @Override 
     public int describeContents() { 
        return 0; 
     }

     @Override 
     public void writeToParcel(Parcel dest, int flags) { 
        dest.writeInt(this.bookId); 
        dest.writeString(this.bookName); 
     }

     protected Book(Parcel in) { 
        this.bookId = in.readInt(); 
        this.bookName = in.readString(); 
     }
     public static final Parcelable.Creator<Book> CREATOR = new P arcelable.Creator<Book>() {
        @Override 
        public Book createFromParcel(Parcel source) { 
           return new Book(source); 
        }

        @Override 
        public Book[] newArray(int size) { 
           return new Book[size]; 
        } 
    }; 
  }

由于AIDL只支持数据类型:基本类型(int,long,char,boolean等),String, CharSequence,List,Map,其他类型必须使用import导入,即使它们可能在同一 个包里,比如上面的Book。 最终IBookManager.aidl 的实现

  // Declare any non-default types here with import statements import com.lvr.aidldemo.Book; 
  interface IBookManager { 
     /**
       * Demonstrates some basic types that you can use as paramet ers 
       * and return values in AIDL. 
       */ 
    void basicTypes(int anInt, long aLong, boolean aBoolean, flo at aFloat, double aDouble, String aString); 
    void addBook(in Book book); List<Book> getBookList(); 
  }

注意: 如果自定义的Parcelable对象,必须创建一个和它同名的AIDL文件,并在其 中声明它为parcelable类型。

Book.aidl

  // Book.aidl 
  package com.lvr.aidldemo; 

  parcelable Book;

以上就是AIDL部分的实现,一共三个文件。 然后Make Project ,SDK为自动为我们生成对应的Binder类。 在如下路径下:
谈谈Android Binder机制及AIDL使用
其中该接口中有个重要的内部类Stub ,继承了Binder 类,同时实现了 IBookManager接口。 这个内部类是接下来的关键内容。

public static abstract class Stub extends android.os.Binder impl ements com.lvr.aidldemo.IBookManager{}

服务端 服务端首先要创建一个Service用来监听客户端的连接请求。然后在Service 中实现Stub 类,并定义接口中方法的具体实现。

  //实现了AIDL的抽象函数 
  private IBookManager.Stub mbinder = new IBookManager.Stub() { 
     @Override 
     public void basicTypes(int anInt, long aLong, boolean aBoole an, float aFloat, double aDouble, String aString) throws RemoteE xception {
        //什么也不做 
     }

     @Override 
     public void addBook(Book book) throws RemoteException { 
        //添加书本 
        if (!mBookList.contains(book)) { 
             mBookList.add(book); } 
     }

     @Override 
     public List<Book> getBookList() throws RemoteException { 
       return mBookList; 
    } 
  };

当客户端连接服务端,服务端就会调用如下方法:

  public IBinder onBind(Intent intent) { 
     return mbinder; 
  }

就会把Stub实现对象返回给客户端,该对象是个Binder对象,可以实现进程间通 信。 本例就不真实模拟两个应用之间的通信,而是让Service另外开启一个进程来 模拟进程间通信。

  <service 
      android:name=".MyService" 
      android:process=":remote"> 
      <intent-filter> 
          <category android:name="android.intent.category.DEFAULT" /> 
          <action android:name="com.lvr.aidldemo.MyService" /> 
      </intent-filter> 
  </service>

android:process=":remote"设置为另一个进程。 &lt;action android:name="com.lvr.aidldemo.MyService"/&gt; 是为了能让其他apk隐式 bindService。通过隐式调用的方式来连接service,需要把category设为default, 这是因为,隐式调用的时候,intent中的category默认会被设置为default。

客户端
首先将服务端工程中的aidl文件夹下的内容整个拷贝到客户端工程的对应位置下, 由于本例的使用在一个应用中,就不需要拷贝了,其他情况一定不要忘记这一步。
客户端需要做的事情比较简单,首先需要绑定服务端的Service。

  Intent intentService = new Intent(); 
  intentService.setAction("com.lvr.aidldemo.MyService"); 
  intentService.setPackage(getPackageName()); 
  intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
  MyClient.this.bindService(intentService, mServiceConnection, BIN 
  D_AUTO_CREATE); 
  Toast.makeText(getApplicationContext(), "绑定了服务", Toast.LENGTH _SHORT).show();

将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的 方法了。

  if (mIBookManager != null) { 
     try {
          mIBookManager.addBook(new Book(18, "新添加的书")); 
          Toast.makeText(getApplicationContext(), mIBookManager.ge 
  tBookList().size()   "", Toast.LENGTH_SHORT).show(); 
     } catch (RemoteException e) {
     e.printStackTrace(); 
     } 
  }
3.AIDL的工作原理

Binder机制的运行主要包括三个部分:注册服务、获取服务和使用服务。 其中注册 服务和获取服务的流程涉及C的内容,由于个人能力有限,就不予介绍了。
本篇文章主要介绍使用服务时,AIDL的工作原理。

①.Binder对象的获取
Binder是实现跨进程通信的基础,那么Binder对象在服务端和客户端是共享的,是 同一个Binder对象。在客户端通过Binder对象获取实现了IInterface接口的对象来调 用远程服务,然后通过Binder来实现参数传递。

那么如何维护实现了IInterface接口的对象和获取Binder对象呢?
服务端获取Binder对象并保存IInterface接口对象 Binder中两个关键方法:

  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对象。 在服务端进程,通过实现 private IBookManager.Stub mbinder = new IBookManager.Stub() {}抽象类,获得Binder对象。 并保存了IInterface对象。

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

客户端获取Binder对象并获取IInterface接口对象 通过bindService获得Binder对象

  MyClient.this.bindService(intentService, mServiceConnection, BIN D_AUTO_CREATE);

然后通过Binder对象获得IInterface对象。

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

其中 asInterface(binder)方法如下:

  public static com.lvr.aidldemo.IBookManager asInterface(android. os.IBinder obj) { 
     if ((obj == null)) { 
         return null; 
     }
     android.os.IInterface iin = obj.queryLocalInterface(DESCRIPT OR);
     if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBook Manager))) { 
         return ((com.lvr.aidldemo.IBookManager) iin); 
     }
     return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); 
   }

先通过 queryLocalInterface(DESCRIPTOR);查找到对应的IInterface对象,然后 判断对象的类型,如果是同一个进程调用则返回IBookManager对象,由于是跨进 程调用则返回Proxy对象,即Binder类的代理对象。

②.调用服务端方法
获得了Binder类的代理对象,并且通过代理对象获得了IInterface对象,那么就可以 调用接口的具体实现方法了,来实现调用服务端方法的目的。 以addBook方法为例,调用该方法后,客户端线程挂起,等待唤醒:

  @Override public void addBook(com.lvr.aidldemo.Book book) th rows android.os.RemoteException 
  { 
      .......... 
      //第一个参数:识别调用哪一个方法的ID 
     //第二个参数:Book的序列化传入数据 
     //第三个参数:调用方法后返回的数据 //最后一个不用管 
     mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply , 0); 
     _reply.readException(); 
     }
     .......... 
  }

省略部分主要完成对添加的Book对象进行序列化工作,然后调用 transact 方 法。

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

  case TRANSACTION_addBook: { 
      data.enforceInterface(DESCRIPTOR); 
      com.lvr.aidldemo.Book _arg0; 
      if ((0 != data.readInt())) { 
          _arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(d ata);
      } else { 
          _arg0 = null; 
      }
     //这里调用服务端实现的addBook方法 
     this.addBook(_arg0); 
     reply.writeNoException(); 
     return true; 
  }

然后在 transact 方法获得 _reply 并返回结果,本例中的addList方法没有返回 值。

客户端线程被唤醒。因此调用服务端方法时,应开启子线程,防止UI线程堵塞,导 致ANR。
顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

PDF和源码获取

谈谈Android Binder机制及AIDL使用