android进阶篇13、进程间通信机制Binder简述

425 阅读6分钟

一、AIDL进程间通信简介

AIDL可以理解为进程间通信的中介;

1、客户端和服务端需要创建相同的AIDL接口文件,如下所示,定义了一个名为BookController的aidl接口文件,另外定义了一个名为Book的aidl实体文件;

// BookController.aidl
package com.sunnyweather.android;
import com.sunnyweather.android.Book;
// Declare any non-default types here with import statements
interface BookController {
    List<Book> getBookList();
    void addBookInOut(inout Book book);
}

// Book.aidl
package com.sunnyweather.android;
// Declare any non-default types here with import statements
parcelable Book;

2、我们需要在客户端和服务端针对实体aidl进行实现,并且包名及类名要一致,如下所示,其实就是实现Parcelable接口,因为在进程间通信需要对象可序列化;

package com.sunnyweather.android;

public class Book implements Parcelable {
    private String name;
    public Book(String name) {
        this.name = name;
    }
    protected Book(Parcel in) {
        name = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @NonNull
    @Override
    public String toString() {
        return "book name: " + name;
    }

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

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

    public void readFromParcel(Parcel dest){
        name = dest.readString();
    }
}

3、以上两步是客户端和服务端进行的同样的操作,下面针对客户端和服务端分别处理;

服务端的处理代码如下,注释1处表示当客户端通过bindService绑定服务端时,服务端会通过onBind方法将IBinder对象返回给客户端,这里返回的对象类型就是AIDL接口文件自动生成的java文件的内部类Stub对象,这个Stud继承了Binder并且实现了步骤1中的BookController接口,因此可以作为IBinder类型对象返回;

注释2处表示我们在服务端Stud的匿名内部类实现了此接口中的两个方法,这样在客户端调用此接口中的方法时就会回调到这里;

public class AIDLService extends Service {
    private List<Book> bookList;
    public AIDLService() {
    }
    @Override
    public IBinder onBind(Intent intent) {  //1
        return stub;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        bookList = new ArrayList<>();
        Book book = new Book("算法导论");
        bookList.add(book);
    }

    private final BookController.Stub stub = new BookController.Stub() {  //2
        @Override
        public List<Book> getBookList() {
            return bookList;
        }

        @Override
        public void addBookInOut(Book book) {
            if (book != null){
                book.setName("服务器改了新书的名字 InOut");
                bookList.add(book);
            }
        }
    };
}

4、客户端的处理代码如下,注释4处的bindService表示客户端与服务端进行绑定,绑定成功会回调到注释1处,我们在注释1处将服务端返回的IBinder对象通过BookController.Stub.asInterface(iBinder)方法获得BookController接口对象,然后在注释2和注释3处调用接口中的两个方法,就会回调到服务端中匿名内部类Stub的内部实现中去;

public class MainActivity extends AppCompatActivity {
    private final String TAG = "ClientMainActivity";
    private BookController bookController;
    private boolean connected;
    private List<Book> bookList;

    private final ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            bookController = BookController.Stub.asInterface(iBinder); //1
            connected = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            connected = false;
        }
    };

    private final View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_getBookList:
                    if (connected){
                        try {
                            bookList = bookController.getBookList(); //2
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        log();
                    }
                    break;
                case R.id.btn_addBook_inout:
                    if (connected){
                        Book book = new Book("这是一本新书");
                        try {
                            bookController.addBookInOut(book); //3
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    };

    private void log() {
        for (Book book: bookList){
            Log.e(TAG, book.toString());
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn_getBookList).setOnClickListener(clickListener);
        findViewById(R.id.btn_addBook_inout).setOnClickListener(clickListener);
        bindServices(); //4
    }

    private void bindServices() {
        Intent intent = new Intent();
        try {
            intent.setPackage("com.sunnyweather.android");
            intent.setAction("com.sunnyweather.android.action");
            bindService(intent,serviceConnection,BIND_AUTO_CREATE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (connected){
            unbindService(serviceConnection);
        }
    }
}

二、进程间通信

1、方式介绍:

进程间通信的方式有共享内存、Binder、Socket、管道、信号量等,各种方式特点如下:

共享内存:数据无需拷贝;但控制复杂,易用性差;访问接入点是开放的,不安全;

Binder:数据拷贝一次;基于C/S架构,易用性好;为每个APP分配UID,同时支持实名和匿名,安全性好;

Socket等其他方式:数据拷贝两次;效率低,开销大;访问接入点是开放的,不安全;

综上所述,Android进程间通信选用的是Binder;

2、内存划分:

内存被操作系统划分为两块,用户空间和内核空间,他们之间是相互隔离的,用户空间专门存放用户程序代码运行的地方,内核空间是内核代码运行的地方;这样即使用户程序崩溃了内核也不会受影响;

32位操作系统的寻址空间为2^32,即总共可访问地址为4G;内核为1G,用户空间为3G;

64位操作系统,低位0-47位才是有效的可变地址(256T),高位48-63位全补0对应的寻址空间是用户空间,高位48-63位全补1对应的寻址空间是内核空间;

3、传统IPC传输数据:

传统IPC在进程之间传输数据时需要拷贝两次;内核空间首先调用copy_from_user()将数据从用户空间拷贝到内核缓存区(第一次拷贝),然后内核空间调用copy_to_user()将数据从内核缓存区拷贝到用户空间(第二次拷贝);

4、Binder传输数据:

Binder在进程之间传输数据时仅需要拷贝一次;首先内核空间通过copy_from_user()将数据拷贝到一块物理内存区域,然后内核空间将自己的一块虚拟空间指向该物理内存,最后接收方用户进程通过mmap()将其虚拟内存指向该物理内存,便可以直接使用该物理内存中的数据了,这样也就实现了仅需一次拷贝;

mmap:Linux通过将一个虚拟内存区域与一个物理内存区域关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping),简称mmap;对文件进行mmap,会在进程的虚拟内存分配地址空间,创建映射关系,实现这样的映射关系后,就可以通过指针的方法读写操作这一段内存,而系统会自动回写到对应的文件磁盘上;

三、Binder框架

1、简介

Binder框架可分为Client端和Server端,根据层级又会分为FrameWork层,JNI层,Native层和Kernel驱动层;

FrameWork层在客户端和服务端的代表是BinderProxy和Binder;Native层在客户端和服务端的代表是BpBinder和BBinder;

2、Binder内核驱动层

在Binder驱动层,主要会调用四个方法:

binder_init 创建dev/binder设备节点

binder_open 获取Binder Drive的文件描述符

binder_mmap 在内核分配一块内存,用于存放数据

binder_ioctl 将IPC数据作为参数传递给Binder Drive

binder_ioctl_write_read 这个方法是binder_ioctl比较重要的方法,在数据读写的时候经常调用

    binder_thread_write 进行写操作时调用的方法

    binder_thread_read 进行读操作时调用的方法

3、传输的数据在内核层的大小限制为4M,应用层限制为1M-8K;

四、service_manager

1、启动service_manager

启动service_manager进程通过service_manager.c的main方法,主要有三步:

binder_open 打开binder驱动,申请128k字节大小的内存空间

binder_become_context_manager 设为守护进程,称为binder的管理者

binder_loop 进入无限循环,处理client端发来的请求

2、获取service_manager

获取service_manager主要通过defaultServiceManager方法,主要有四步:

ProcessState::self

ProcessState::getContextObject

Interface_cast

BpServiceManager

3、addService流程

addService主要通过以下三步流程执行:

SystemServer.run

getIServiceManager

SMP.addService

用命令总结(同步状态下):首先CLient发送BC_TRANSACTION命令给Binder驱动;接着Binder驱动发送BR_TRANSACTION_COMPLETE命令给客户端(客户端进入休眠等待状态),发送BR_TRANSACTION命令给Server端,Server端从休眠中被唤醒执行操作;然后执行完操作之后,发送BC_REPLY命令给Binder驱动;最后Binder驱动发送BR_TRANSACTION_COMPLETE命令给Server端(服务端进入休眠等待状态),发送BR_REPLY命令给客户端,将客户端从休眠等待中唤醒;

BC代表Binder Command;BR代表Binder Return;