Binder与AIDL使用详解

本人博客已经迁移到nasdaqgodzilla.github.io

Binder 原理

1、概述

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

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

为什么要使用 Binder?

性能方面

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

安全方面

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

2、 Binder

IPC 原理

从进程角度来看 IPC(Interprocess Communication)机制

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

Binder 原理

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

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 是服务端。

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

Binder 运行的实例解释

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

WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE);

View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);

wm.addView(view, layoutParams);


注册服务 (addService): 在 Android 开机启动过程中,Android 会初始化系统的各种 Service,并将这些 Service 向 ServiceManager 注册(即让 ServiceManager 管理)。这一步是系统自动完成的。

获取服务 (getService): 客户端想要得到具体的 Service 直接向 ServiceManager 要即可。客户端首先向 ServiceManager 查询得到具体的 Service 引用,通常是 Service 引用的代理对象,对数据进行一些处理操作。即第 2 行代码中,得到的 wm 是 WindowManager 对象的引用。

使用服务: 通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即第 6 行调用 WindowManager 的 addView 函数,将触发远程调用,调用的是运行在 systemServer 进程中的 WindowManager 的 addView 函数。

使用服务的具体执行过程

  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 文件

默认生成格式

interface IBookManager {
    
    void basicTypes(int anInt, long aLong, boolean aBoolean, float 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 Parcelable.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 的实现

import com.lvr.aidldemo.Book;

interface IBookManager {
    
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void addBook(in Book book);

    List<Book> getBookList();

}


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

Book.aidl

package com.lvr.aidldemo;

parcelable Book;


以上就是 AIDL 部分的实现,一共三个文件。

然后 Make Project ,SDK 为自动为我们生成对应的 Binder 类。

在如下路径下:

其中该接口中有个重要的内部类 Stub ,继承了 Binder 类,同时实现了 IBookManager 接口。 这个内部类是接下来的关键内容。

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


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

private IBookManager.Stub mbinder = new IBookManager.Stub() {
    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        
    }

    @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:
    android:process=":remote">
    <intent-filter>
        <category android: />
        <action android: />
    </intent-filter>
</service>


android:process=":remote"设置为另一个进程。<action android:/>是为了能让其他 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, BIND_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.getBookList().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) 
    ..........................
}


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, BIND_AUTO_CREATE);


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

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        
        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(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBookManager))) {
        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) throws android.os.RemoteException
    {
        ..........
        
        
        
        
        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(data);
    } else {
        _arg0 = null;
    }
    
    this.addBook(_arg0);
    reply.writeNoException();
    return true;
}


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

客户端线程被唤醒。因此调用服务端方法时,应开启子线程,防止 UI 线程堵塞,导致 ANR。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值