【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处于同一个进程,直接返回,如果不是,返回一个代理对象。

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

Java 复制代码
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方法返回一个代理对象,再调用方法进行交互。
Java 复制代码
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机制入门学习笔记

相关推荐
阿巴斯甜6 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker6 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95277 小时前
Andorid Google 登录接入文档
android
黄林晴9 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab21 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android