美文网首页Android开发Android进阶之路Android技术知识
再不学这干货就废了!Framework的进程间通信(Binder

再不学这干货就废了!Framework的进程间通信(Binder

作者: 码农的地中海 | 来源:发表于2022-06-07 22:16 被阅读0次

    一、Binder是什么?

    Binder是Android中一种进程间通信机制。我们平时使用的Activity、Service、Broadcast Receive等组件之间的通信,以及组件与系统层的AMS、PMS等服务间的通信,都是基于Binder的进程间通信机制来实现的,相当于是Android中的血管,可见Binder机制在Android中非常重要。这里要知道的是,Zygote进程的IPC采用的是Socket机制,因为这时Binder还未初始化。

    从代码层面来说,Binder是一个能发起通信的Java类。AIDL中Stub类即继承自Binder,具有跨进程的能力。

    Binder是一个虚拟的物理设备驱动,提供跨进程的能力。

    二、为什么要使用Binder

    Android系统的基础是Linux内核,而Linux中实现IPC的机制有管道、消息队列、共享内存、Socket、信号量、信号这些,为什么Android还要另起炉灶呢?主要是性能、****安全、易用性等方面的原因。

    性能上来说数据拷贝次数越少越好,什么是拷贝下文再介绍。传统IPC机制(Socket、管道、消息队列等)都是拷贝两次,共享内存虽然无需拷贝,但会有安全、死锁、易用性差等问题,Binder只需拷贝一次,因此性能仅次于共享内存优于Socket。

    安全性上来说Binder会为每个APP分配唯一的UID,Binder根据UID可以找到对应APP,传统IPC依赖上层协议是不安全的,无法获得对方的UID从而不能鉴别身份。传统IPC访问接入点是开放的,相当于谁都可以访问;Binder既有实名服务又有匿名服务,实名就跟传统IPC一样,谁都可以访问,AMS、WMS都是实名服务,匿名类似于打滴滴,用户直接联系不到司机,需要通过滴滴(系统)平台拿到司机号码,通过系统拿到服务的代理对象,再通过代理对象找到服务。Binder实名和匿名区别在于有没有在ServiceManager注册,注册了即为实名,没注册称为匿名,我们自己使用AIDL等方式一般为匿名。

    几种IPC机制对比

    QQ截图20220607193216.png

    三、IPC机制原理

    传统IPC机制如何实现跨进程通信

    一个进程中的内存被操作系统分为用户空间(用户态)和内核空间(内核态),用户空间是用户程序代码运行的地方,内核空间是系统内核代码运行的地方,为了保护用户不能直接操作内核,两者是隔离的,用户空间可以申请系统调用来传递数据,从用户空间拷贝到内核空间通过copy_from_user函数(内核态), 从内核空间拷贝到用户空间通过copy_to_user函数(用户态),用户态和内核态之间转换有一个上下文切换非常消耗时间。

    数据拷贝,指的就是copy_from_user或copy_to_user调用了几次系统调用(syscall)

    虚拟内存和物理内存(内存条)关系,虚拟内存通过内存管理单元(MMU管理映射)映射到物理内存。所有进程内核空间映射到同一块物理内存(内存共享),每个用户空间则映射到不同的物理内存。

    传统IPC机制两次拷贝:调用一次系统调用将数据从用户空间拷贝到内核空间,然后通过copy_to_user将数据从内核空间拷贝到另一进程的用户空间,完成进程间通信,如下图所示

    QQ截图20220607193226.png

    Binder IPC机制原理

    传统IPC机制需要两次拷贝,而Binder IPC只需一次拷贝,如何实现的?

    内存映射(mmap):Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。

    实现映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。

    Binder就是通过内存映射发送端内核空间接收端用户空间指向同一块物理内存,这样就可以共享这块内存,当数据发送端通过系统调用将数据copy到内核空间,因此也就相当于把数据发送到了接收端的用户空间,这样就只有发送端的一次系统调用,所以只有一次拷贝,如下图所示:

    QQ截图20220607193056.png

    四、使用示例

    新建三个文件,Book.java、Book.aidl和IBookManager.aidl,代码如下: Book.java

    package com.example.runningh.myapplication.aidl;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Book implements Parcelable {
        public int bookId;
        public String bookName;
    
        public Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(bookId);
            dest.writeString(bookName);
        }
    
        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];
            }
        };
    
        private Book(Parcel in) {
            bookId = in.readInt();
            bookName = in.readString();
        }
    }
    复制代码
    

    Book .java是一个表示图书信息的类,实现了Parcelable接口。

    Book.aidl:

    package com.example.runningh.myapplication.aidl;
    
    parcelable Book;
    复制代码
    

    Book.aidl是Book类在AIDL中的声明。

    IBookManager.aidl:

    package com.example.runningh.myapplication.aidl;
    
    import com.example.runningh.myapplication.aidl.Book;
    import com.example.runningh.myapplication.aidl.IOnNewBookArrivedListener;
    
    interface IBookManager {
    
        List<Book> getBookList();
    
        void addBook(in Book book);
    }
    复制代码
    

    IBookManager.adil是我们定义的一个接口,里面有两个方法:getBookList和addBook,其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书。

    注意:在IBookManager.aidl中要手动导入Book类,不支持自动导入,这是AIDL的特殊之处。

    通过执行build操作后,系统为IBookManager.aidl生成的Binder类,在gen目录下的source文件夹下可找到IBookManager.java类。

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: /Users/RunningH/Documents/TencentGame/ThirdParty/ThemeSkinning/MyApplication2/app/src/main/aidl/com/example/runningh/myapplication/aidl/IBookManager.aidl
     */
    package com.example.runningh.myapplication.aidl;
    public interface IBookManager extends android.os.IInterface {
        /** Local-side IPC implementation stub class. */
        public static abstract class Stub extends android.os.Binder implements com.example.runningh.myapplication.aidl.IBookManager {
            private static final java.lang.String DESCRIPTOR = "com.example.runningh.myapplication.aidl.IBookManager";
            
            /** Construct the stub at attach it to the interface. */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
            
            /**
             * Cast an IBinder object into an com.example.runningh.myapplication.aidl.IBookManager interface,
             * generating a proxy if needed.
             */
            public static com.example.runningh.myapplication.aidl.IBookManager asInterface(android.os.IBinder obj) {
                if ((obj==null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin!=null)&&(iin instanceof com.example.runningh.myapplication.aidl.IBookManager))) {
                    return ((com.example.runningh.myapplication.aidl.IBookManager)iin);
                }
                return new com.example.runningh.myapplication.aidl.IBookManager.Stub.Proxy(obj);
            }
            
            @Override public android.os.IBinder asBinder() {
                return this;
            }
            
            @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(DESCRIPTOR);
                        return true;
                    }
                    case TRANSACTION_getBookList: {
                        data.enforceInterface(DESCRIPTOR);
                        java.util.List<com.example.runningh.myapplication.aidl.Book> _result = this.getBookList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addBook: {
                        data.enforceInterface(DESCRIPTOR);
                        com.example.runningh.myapplication.aidl.Book _arg0;
                        if ((0!=data.readInt())) {
                            _arg0 = com.example.runningh.myapplication.aidl.Book.CREATOR.createFromParcel(data);
                        }
                        else {
                            _arg0 = null;
                        }
                        this.addBook(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
        
            private static class Proxy implements com.example.runningh.myapplication.aidl.IBookManager {
                private android.os.IBinder mRemote;
                
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
                
                @Override public android.os.IBinder asBinder() {
                    return mRemote;
                }
                
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
                
                @Override public java.util.List<com.example.runningh.myapplication.aidl.Book> getBookList() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    java.util.List<com.example.runningh.myapplication.aidl.Book> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(com.example.runningh.myapplication.aidl.Book.CREATOR);
                    }
                    finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
                
                @Override public void addBook(com.example.runningh.myapplication.aidl.Book book) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        if ((book!=null)) {
                            _data.writeInt(1);
                            book.writeToParcel(_data, 0);
                        }
                        else {
                            _data.writeInt(0);
                        }
                        mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                        _reply.readException();
                    }
                    finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
        }
    
    
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        
        public java.util.List<com.example.runningh.myapplication.aidl.Book> getBookList() throws android.os.RemoteException;
        public void addBook(com.example.runningh.myapplication.aidl.Book book) throws android.os.RemoteException;
    }
    复制代码
    

    首先这个类声明了两个方法getBookList和addBook,显然这就是我们在IBookMananger.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理Proxy来完成。

    我们看一下怎么使用这个类,通过调用过程来熟悉其中的方法。

    我们新建一个工程并在新建BookManagerActivity、BookManagerService并将BookManagerService放在另外一个进程中(在manifest中声明)。在BookManagerActivity通过BindService的方式和BookManagerService绑定。

    package com.example.runningh.myapplication.aidl;
    
    import android.app.Activity;
    import android.app.ListActivity;
    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.RemoteException;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    import com.example.runningh.myapplication.R;
    
    import java.util.List;
    
    /**
     * Created by RunningH on 2017/11/26.
     */
    
    public class BookManagerActivity extends Activity {
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager iBookManager = IBookManager.Stub.asInterface(service); //注意这里调用了Stub的asInterface方法,通过服务端的IBinder对象找到
                try {
                    List<Book> list = iBookManager.getBookList();
                    Log.i("ABC", "query book list, list type:" + list.getClass().getCanonicalName());
                    Log.i("ABC", "query book list:" + list.toString());
                    Book newBook = new Book(3, "HTML");
                    iBookManager.addBook(newBook);
                    Log.i("ABC", "add book:" + newBook);
                    List<Book> newList = iBookManager.getBookList();
                    Log.i("ABC", "query book list:" + newList.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book_manager);
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(mConnection);
        }
    }
    复制代码
    

    BookManagerService.java:

    package com.example.runningh.myapplication.aidl;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.RemoteException;
    
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
     * Created by RunningH on 2017/11/26.
     */
    
    public class BookManagerService extends Service {
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    
        private Binder mBinder = new IBookManager.Stub() {
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1, "Android"));
            mBookList.add(new Book(2, "IOS"));
        }
        
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }
    复制代码
    
    • BookManagerService中new了一个Stub对象,并重写addBook和getBookList方法。这样Stub的构造方法被调用,构造方法中调用了Binder的attachInterface将该Binder对象(Stub就是一个Binder对象)和DESCRIPTOR(Binder的唯一标识,一般用Binder的类名表示)联系起来。
    • 上面生成的Stub对象(也就是Binder对象)通过BinderService的方式传递给客户端,也就是BookManagerActivity的mConnection的onServiceConnected方法中service对象。通过调用IBookManager.Stub.asInterface方法得到客户端所需要的AIDL接口类型对象。这是区分进程的,如果客户端和服务端处于同一个进程,那么执行queryLocalInterface(DESCRIPTOR)返回就不为空(还记得上面生成Stub对象的时候将该对象和DESCRIPTOR绑定吗?这里就是取出来),所以此方法返回的就是服务端的Stub对象本身。如果是不同的进程则返回的是系统封装或的Stub.proxy对象。对应于下面的则是new了一个Stub.Proxy对象。
    public static com.example.runningh.myapplication.aidl.IBookManager asInterface(android.os.IBinder obj) {
         if ((obj==null)) {
             return null;
         }
         android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
         if (((iin!=null)&&(iin instanceof com.example.runningh.myapplication.aidl.IBookManager))) {
              return ((com.example.runningh.myapplication.aidl.IBookManager)iin);
        }
        return new com.example.runningh.myapplication.aidl.IBookManager.Stub.Proxy(obj);
    }
    复制代码
    
    • 先看如果处于同一个进程的情况,得到服务端的Stub对象后,直接调用getBookList和addBook方法。因为Stub对象实现了IBookManager接口,并实现了其中的getBookList和addBook方法,所以这里实际调用的是getBookList和addBook方法。这样就实现了客户端和服务端的交互。
    • 再来看不同进程的情况,我们得到的是一个Stub.Proxy对象。在客户端调用getBookList和addBook方法,实际上调用的是Stub.Proxy对象的getBookList和addBook方法。
    private static class Proxy implements com.example.runningh.myapplication.aidl.IBookManager {
        private android.os.IBinder mRemote;
    
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
    
        @Override public android.os.IBinder asBinder() {
            return mRemote;
        }
    
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }
    
        @Override public java.util.List<com.example.runningh.myapplication.aidl.Book> getBookList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.example.runningh.myapplication.aidl.Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.example.runningh.myapplication.aidl.Book.CREATOR);
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    
        @Override public void addBook(com.example.runningh.myapplication.aidl.Book book) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((book!=null)) {
                    _data.writeInt(1);
                    book.writeToParcel(_data, 0);
                }
                else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                _reply.readException();
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
    复制代码
    
    • getBookList方法中调用了mRemote.transact方法,第一个参数标识调用哪一个方法,第二个参数标识需要传递进去的参数,第三个参数标识需要返回的数据,第四个参数一般为0。调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起(等待数据返回),然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果。
    • addBook方法调用了mRemote.transact方法,参数的意思同上一样。由于addBook不需要返回值,所以它不需要从_reply中取出返回值。
    • 上面两个方法都调用了transact方法,那么该方法被调用后会触发服务端的Stub对象的onTransact方法被调用。
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.example.runningh.myapplication.aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                com.example.runningh.myapplication.aidl.Book _arg0;
                if ((0!=data.readInt())) {
                    _arg0 = com.example.runningh.myapplication.aidl.Book.CREATOR.createFromParcel(data);
                }
                else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    复制代码
    
    • 通过onTransact方法,我们通过code参数作为标识具体调用的哪一个方法,比如TRANSACTION_getBookList就是调用getBookList方法,这里做的操作是调用Stub对象的getBookList方法并且将结果写入到reply(如果该操作需要结果返回的话)中然后返回。

    五、结尾

    Binder 的设计非常优秀,分析 Binder 的实现也是一件十分有趣的事情,本文作为对 Binder 深入研究的「引入」,站在了一个很高的角度俯视整套系统,并没有对其中的细节做深入探讨,如果大家对内核开发或者操作系统底层有兴趣的话,可以看看framework进程间通信(Binder IPC机制),相信也会有不少收获,本文到这里就先结束了。更多framework学习资料:Frame Work源码解析手册

    相关文章

      网友评论

        本文标题:再不学这干货就废了!Framework的进程间通信(Binder

        本文链接:https://www.haomeiwen.com/subject/thlymrtx.html