【Android】Binder机制浅析

【Android】Binder机制浅析

1、前言

BInder机制是Android系统提供的跨进程通信机制。本篇文章会从Linux的基础知识开始介绍,从基础概念引出Binder机制,接着分析Binder的通信模型和原理,最后将会手动实现AIDL完成进程间的通信。侧重点在于原理和使用上,适合初学者。

2、Binder原理

Android是基于Linux内核的,Linux提供了管道、消息队列、信号量、内存共享、套接字等跨进程方式,而Android为什么采用了Binder作为主要机制呢?

一、传输性能好

  • Socket:是一个通用接口,导致其传输效率低,开销大。
  • 共享内存:虽然在传输时不需要拷贝数据,但其控制机制复杂
  • Binder:复杂数据类型传递可复用内存,需要拷贝1次数据
  • 管道和消息队列:采用存储转发方式,至少需要拷贝2次数据,效率低

二、安全性高

  • 传统的进程:通信方式对于通信双方的身份并没有做出严格的验证,只有在上层协议上进行架设
  • Binder机制:从协议本身就支持对通信双方做身份校验,因而大大提升了安全性

2.1 Binider实现机制

2.1.1 进程隔离

进程空间划分:用户空间内核空间

  • 用户空间:表示进程运行在一个特定的操作模式中,没有接触物理内存或设备的权限
  • 内核空间:表示独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限
  • 每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。例如,对应一个4GB的虚拟内存空间,其中3GB是用户空间,1GB是内核空间,内核空间的大小是可以通过参数配置调整的。
  • 对于用户空间,不同进程之间是不能共享的,而内核空间却是可共享的。

简单来说,内核空间是系统内核运行的空间,而用户空间是用户程序运行的空间。每个进程都有自己的虚拟内存空间,每个进程只能操作自己的虚拟内存空间,只有操作系统才有权限操作物理内存空间。进程隔离保证了每个进程的内存安全,但在大多情况下,不同进程间的数据通信是不可避免的,因此操作系统必须提供跨进程通信机制。

2.1.2 系统调用

系统调用是用户态切换到内核态的唯一合法途径

抽象来看,操作系统中安全边界的概念就像环路的概念一样,一个环上持有一个特定的权限组,Intel支持四层环,但是Linux只使用了其中的两层环(0号环持有全部权限,3号环持有最少权限,1号环和2号环未使用),系统进程运行在0号环,用户进程运行在3号环。如果一个用户进程需要其他高级权限,其必须从三号环过渡到0号环,过渡需要通过一个安全参数检查的网关,这种过渡被称为系统调用,在执行过程中会产生一定数量的计算开销。所以,用户空间要访问内核空间唯一的方式就是系统调用。

2.1.3 内核模块/驱动

如前面所说,跨进程通信是需要内核空间做支持的。传统的IPC机制如管道、Socket都是内核的一部分。但Binder不是内核的一部分,是如何做到跨进程通信的呢?

Linux的动态内核可加载模块机制解决了这个问题:

  • 模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行
  • Android系统就可以动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。在Android系统中,这个运行在内核空间中,负责各个用户进程通过Binder实现通信的内核模块就叫Binder驱动

那用户进程之间是如何通过这个内核模块(Binder驱动)来实现通信的呢?这里当然不是从发送方进程拷贝到内核缓存区,再把数据从内核缓存区拷贝到接收方进程,而是通过内存映射

  • 内存映射简单地讲就是把用户空间的一块内存区域映射到内核空间,映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间。
  • 反之内核空间对这块区域的修改也能直接反映到用户空间。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。

一次完整的Binder IPC 通信过程通常是这样:

  • Binder 驱动在内核空间创建一个数据接收缓存区;
  • 然后在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
  • 发送方进程通过系统调用 copy_from_user() 将数据拷贝到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

最后,简单总结一下:

  1. Linux的虚拟内存机制导致内存的隔离,进而导致进程隔离
  2. 进程隔离的出现导致对内存的操作被划分为用户空间和内核空间
  3. 用户空间需要跨权限去访问内核空间,必须使用系统调用去实现
  4. 系统调用需要借助内核模块/驱动去完成

2.2 Binder通信模型

Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动。架构图如下所示:

前三者是在用户空间的,也就是彼此之间无法直接进行交互,Binder驱动是属于内核空间的,属于整个通信的核心。

四个角色

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

通信原理

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

概括一下,首先,Service中的Binder实体对象,将自己的引用注册到ServiceManager,Client通过特定的key和这个引用进行绑定,ServiceManager内部自己维护一个类似MAP的表来一一对应,通过这个key就可以在ServiceManager中拿到Service中Binder的引用。

但是这里存在一个问题,ServiceManager和Service也属于两个不同的进程,Service在ServiceManager注册也涉及到进程间通信吗,好像变成无限死循环了?看看下面这张图:

当ServiceManager作为服务端的时候,它提供的Binder比较特殊,它没有名字也不需要注册,它的固定引用号就是0,所有进程都知道ServiceManager的Binder引用号是0。由Binder驱动自动管理,不需要像其他服务那样先注册再使用。这样,一个Service若要向ServiceManager中注册自己的Binder引用就可以通过硬编码的0引用号和ServiceManager中的Binder通信。

那Client怎么拿到Service中的Binder对象呢?图中可以很清晰的看出整个过程,Client原本从ServiceManager中拿到Binder的引用,通过Binder驱动层的处理之后,返回给Client一个代理对象,实际上如果Client和Service处于同一个进程,返回的就是Binder对象。如果不在同一个进程,返回给Client的就是一个代理对象(proxy)。

3、手动实现进程通信

在学习《安卓开发艺术探索》中关于Binder时,首先介绍由AIDL来实现进程间通信,不过相信大多数朋友和我一样,第一次看到系统自动生成的AIDL代码时是懵逼状态的,涉及Stub类,还有Proxy类。这里我们就手动实现一个通信代码,加深对Binder机制的理解。

  1. 创建Book实体类,使用Parcelable接口实现序列化
java 复制代码
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

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;
    }

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

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

    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];
        }
    };

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
}
  1. 声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中有一个asBinder方法。实现如下:
java 复制代码
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;

import java.util.List;

public interface IBookManager extends IInterface {
    static final String DESCRIPTOR = "com.example.ipctest.IBookManager";
    static final int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 0;
    static final int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1;
    public List<Book> getBookList () throws RemoteException;
    public void addBook (Book book) throws RemoteException;
}

在接口中声明了一个Binder描述符和另外两个id,这两个id分别表示的是getBookList和addBook方法。另外,如果再想要添加方法,只需再声明一个id,然后按固定模式声明新方法即可。

  1. 实现一个Service中的Binder实体对象,继承Binder并实现上面定义好的服务接口。
java 复制代码
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Collections;
import java.util.List;

public class BookManagerImpl extends Binder implements IBookManager {
    public BookManagerImpl() {
        this.attachInterface(this, DESCRIPTOR);
    }
    public static IBookManager asInterface(IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IBookManager))) {
            return ((IBookManager) iin);
        }
        return new Proxy(obj);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                List<Book> result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                Book arg0;
                if ((0 != data.readInt())) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                } else {
                    arg0 = null;
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public List<Book> getBookList() throws RemoteException {
        // TODO 待实现
return Collections.emptyList();
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        // TODO 待实现
}
}

首先看看asInterface方法,Binder驱动传来的IBinder对象,通过queryLocallnterface方法,查找本地Binder对象,如果返回的就是IBookManager,说明Client和Server处于同一个进程,直接返回,如果不是,返回一个代理对象。

作为代理对象,也要实现服务接口:

ini 复制代码
private static class Proxy implements IBookManager {
    private IBinder mRemote;

    public Proxy(IBinder mRemote) {
        this.mRemote = mRemote;
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }

    public java.lang.String getInterfaceDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public List<Book> getBookList() throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        List<Book> result;
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_getBookList, data, reply, 0);
            reply.readException();
            result = reply.createTypedArrayList(Book.CREATOR);

        } finally {
            reply.recycle();
            data.recycle();
        }
        return result;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if ((book != null)) {
                data.writeInt(1);
                book.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            mRemote.transact(TRANSACTION_addBook, data, reply, 0);
            reply.readException();
        } finally {
            reply.recycle();
            data.recycle();
        }
    }
}

这里代理对象实质上就是Client最终拿到的代理服务,通过这个代理对象就可以和Server进行通信了,首先通过Parcel将数据序列化,然后调用remote.transact()将方法code和data传输过去,对应的回调在Server的onTransact()中。

  1. 然后就是Server进程,onbind方法返回mStub对象,也就是Server中的Binder实体对象。
java 复制代码
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class MyService extends Service {
    private static final String TAG = "MyService";
    private List<Book> mBook = new ArrayList<>();

    @Override
    public void onCreate() {
        mBook.add(new Book());
        super.onCreate();
    }

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }

    private BookManagerImpl mStub = new BookManagerImpl() {
        @Override
        public void addBook(Book book) throws RemoteException {
            if (book == null) {
                book = new Book();
                Log.d(TAG, "null obj");
            }
            mBook.add(book);
            Log.d(TAG, mBook.size() + "");
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBook;
        }
    };
}
  1. 最后我们在客户端进程,bindService传入一个ServiceConnection对象,在与服务端建立连接时,通过我们定义好的BookManagerImpl的asInterface方法返回一个代理对象,再调用方法进行交互。
ini 复制代码
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private boolean isConnect = false;
    private static final String TAG = "MainActivity";
    private IBookManager iBookManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        start();
        findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (iBookManager == null){
                    Log.d(TAG,"connect error");
                    return;
                }
                try {
                    iBookManager.addBook(new Book());
                    Log.d(TAG,iBookManager.getBookList().size() + "");
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    private void start() {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"connect success");
            isConnect = true;
            iBookManager = BookManagerImpl.asInterface(service);
            List<Book> bookList = null;
            try {
                bookList = iBookManager.getBookList();
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
            Log.d(TAG,bookList.size() + "");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"connect failed");
            isConnect = false;
        }
    };

}

这样就实现了一次进程间的交互。建议大家手写一遍AIDL进程间通信的实现代码,加深对Binder通信的理解。

内容参考:

《安卓开发艺术探索》 - 任玉刚

3分钟带你看懂android的Binder机制

「Android」Binder机制入门学习笔记

相关推荐
不会写代码的猴子1 小时前
安卓兼容性框架变更记录
android
无风之翼1 小时前
android12下拉菜单栏界面上方显示无内容
android·java
6***94152 小时前
MySQL 字符串日期格式转换
android·数据库·mysql
p***q782 小时前
MySQL——用户管理
android·mysql·adb
g***86692 小时前
MySQL - Navicat自动备份MySQL数据
android·数据库·mysql
q***01772 小时前
【MySQL】数据类型
android·数据库·mysql
大数据女孩_Aimee2 小时前
AndroidAutoOverUsbInteractiveHostTest FAIL
android·测试用例
c***87192 小时前
Tomcat10下载安装教程
android·前端·后端
hqk2 小时前
鸿蒙 ArkUI 从零到精通:基础语法全解析
android·前端·harmonyos