专栏首页Android 进阶Binder 进程通信

Binder 进程通信

IPC的原理

引用Gityuan大佬的一小段话 从进程角度来看IPC

每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。

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

为什么采用Binder进行通信:

  1. 传输效率高、可操作性强传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。相比socket只需要拷贝一次即可,传输效率就高了从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程, 由于共享内存操作复杂,综合来看,Binder的传输效率是最好的。
  2. 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好
  3. 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。

编写AIDL文件

编写Aidl文件时,需要注意下面几点:

  1. 接口名和aidl文件名相同。
  2. 接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
  3. Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、 CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作 为参数或返回值,自定义类型必须实现Parcelable接口。
  4. 自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
  5. 在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。
  6. Java原始类型默认的标记为in,不能为其它标记。

还需要注意:客户端调用远程服务方法,被调用方法运行在服务端binder线程池中,客户单线程会被挂起,这时候如果服务端线程比较耗时,那么客户单就会长期阻塞在这里,如果客户端运行在UI线程,那么就会出现ANR,所以需要开一个子线程让客户端调用远程服务方法。

在Project视图下选中包名,右键New->AIDL->AIDL File,默认文件名即可。随即生成该文件

AIDL 是什么

AIDL(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制。

我们可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。

在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。

编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。

通过这种机制,我们只需要写好 aidl 接口文件,编译时系统会帮我们生成 Binder 接口。

IPC:进程间通信或跨进程通信,指的是两个进程之间进行数据交互

多进程之间通信是IPC一个使用场景

使用多进程,只需给四大组件指定android:process属性;

多进程会造成几个问题

  1. 静态变量或单列模式完全失效;
  2. 线程无法进行同步,同步机制失效;
  3. sharepreference可靠性下降;
  4. Application会多次创建;

Android 会为每一个应用或进程分配一个独立的虚拟机,每一个虚拟机在内存分配上有不同的地址空间。当一个组件跑在一个新的进程中,就重新启动了应用,多次创建Application.

AIDL 支持的数据类型

共 4 种:

  1. Java 的基本数据类型
  2. List 和 Map
    • 元素必须是 AIDL 支持的数据类型
    • Server 端具体的类里则必须是 ArrayList 或者 HashMap
  3. 其他 AIDL 生成的接口
  4. 实现 Parcelable 的实体

AIDL 如何编写

AIDL 的编写主要为以下三部分:

  1. 创建 AIDL
    • 创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
    • 新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
    • Make project ,生成 Binder 的 Java 文件
  2. 服务端
    • 创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法
    • 在 onBind() 中返回
  3. 客户端
    • 实现 ServiceConnection 接口,在其中拿到 AIDL 类
    • bindService()
    • 调用 AIDL 类中定义好的操作请求

AIDL 实例

下面以实例代码演示一个 AIDL 的编写。

创建 AIDL

创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化

package com.ztz.androidhighroad;
 
import android.os.Parcel;
import android.os.Parcelable;
 
public class Book implements Parcelable {
    private int bookId;
    private String bookName;
 
    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
 
    @Override
    public int describeContents() {
        return 0;
    }
 
    /**
     * 序列化对象
     * @param dest
     * @param flags
     */
    @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();
    }
 
@Override
public String toString() {   
        return "Book{
" +  "bookId=" + bookId + ", bookName='" + bookName + '\'' + '}'
;
        }
}

实现 Parcelable 接口是为了后序跨进程通信时使用。

注意 实体类所在的包名。

新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件

在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:

// Book.aidl
package com.ztz.androidhighroad;
// Declare any non-default types here with import statements
//和声明的实体类在一个包类
parcelable Book;

在其中声明映射的实体类名称与类型

注意,这个 Book.aidl 的包名要和实体类包名一致。

然后创建接口 aidl 文件

// IBookManager.aidl
package com.ztz.androidhighroad;
import com.ztz.androidhighroad.Book;
// Declare any non-default types here with import statements
interface IBookManager {    
/**     
    * Demonstrates some basic types that you can use as parameters     
    * and return values in AIDL.     
    */    
List<Book> getBookList();    
void add(in Book book);
}

在接口 aidl 文件中定义将来要在跨进程进行的操作,上面的接口中定义了两个操作:

  • addBook: 添加Book
  • getBookList:获取 Book列表

需要注意的是

  • 非基本类型的数据需要导入,比如上面的Book,需要导入它的全路径。
  • 这里的 Book是 Book.aidl,然后通过Book.aidl 又找到真正的实体 Book类。
  • 方法参数中,除了基本数据类型,其他类型的参数都需要标上方向类型。
  • in(输入), out(输出), inout(输入输出)。

然后Make Project ,生成 Binder 的 Java 文件

AIDL 真正的强大之处就在这里,通过简单的定义 aidl 接口,然后编译,就会为我们生成复杂的 Java 文件。

点击 Build -> Make Project,然后等待构建完成。最后显示生成的文件如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: E:\\test\\AndroidHighRoad\\app\\src\\main\\aidl\\com\\ztz\\androidhighroad\\IBookManager.aidl
 */
package com.ztz.androidhighroad;
// Declare any non-default types here with import statements
 
public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
          */
        public static abstract class Stub extends android.os.Binder implements com.ztz.androidhighroad.IBookManager {
        //Binder的唯一标识,一般用于当前类名
        private static final java.lang.String DESCRIPTOR = "com.ztz.androidhighroad.IBookManager";
 
        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
 
        /**
         * 将服务端Binder对象转换成客户端所需要的AIDL接口对象,如果客户端与服务端位于同一个进程,就返回服务端Stub对象本身,否则返回Stub.Proxy对象
         * Cast an IBinder object into an com.ztz.androidhighroad.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.ztz.androidhighroad.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.ztz.androidhighroad.IBookManager))) {
                return ((com.ztz.androidhighroad.IBookManager) iin);
            }
            return new com.ztz.androidhighroad.IBookManager.Stub.Proxy(obj);
        }
 
        /**
         * 返回当前binder对象
         */
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
 

这个方法运行在服务端Binder线程池中,但客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理,服务端通过code获取请求方法,然后从data中取出目标方法所需的参数, 执行目标方法完毕后,通过reply写入返回值,返回数据并唤醒客户端。如果onTransact方法返回false,那么客户端就请求失败,可以利用这个做权限验证,让不希望的进程无法远程调用我们的服务

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.ztz.androidhighroad.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(descriptor);
                    com.ztz.androidhighroad.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.ztz.androidhighroad.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.add(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }
 
        private static class Proxy implements com.ztz.androidhighroad.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;
        ​    }
 
        ​    /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
                  */
                @Override
                public java.util.List<com.ztz.androidhighroad.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.ztz.androidhighroad.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.ztz.androidhighroad.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
                }
 
        ​    @Override
        ​    public void add(com.ztz.androidhighroad.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_add, _data, _reply, 0);
        ​            _reply.readException();
        ​        } finally {
        ​            _reply.recycle();
        ​            _data.recycle();
        ​        }
        ​    }
        }
 
        //声明两个整型的id来表示接口中定义的方法
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
 
​    /**
​     * Demonstrates some basic types that you can use as parameters
​     * and return values in AIDL.
​          */
​        public java.util.List<com.ztz.androidhighroad.Book> getBookList() throws android.os.RemoteException;
 
​    public void add(com.ztz.androidhighroad.Book book) throws android.os.RemoteException;
}

编写服务端类

/**
 * 服务端类
 * @author Yif
 */
public class BinderService extends Service {
 
    private final String TAG = this.getClass().getSimpleName();
    private List<Book> mBooks;
 
    private final IBookManager.Stub mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBooks;
        }
 
        @Override
        public void add(Book book) throws RemoteException {
            mBooks.add(book);
        }
    };
 
    /**
     * 客户端与服务端绑定成功后就可以回调,返回mBinder对象,客户端就可以远程调用服务端方法,实现通讯
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mBooks = new ArrayList<>();
        return mBinder;
    }
}

需要在AndroidManifest中注册服务

<service android:name=".service.BinderService"
    android:process=":remote"/>

编写客户端类

/**
 * 客户端类
 *
 * @author Yif
 */
public class BinderActivity extends AppCompatActivity {
    private IBookManager iBookManager;
 
    private TextView tv;
 
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //将服务端Binder对象转换成客户端所需要的AIDL接口类型,如果客户端与服务端在同一个进程,返回就是服务端Stub对象本身,否则返回是系统
            // 封装后的Stub.proxy对象
            iBookManager = IBookManager.Stub.asInterface(service);
            try {
                //给binder设置死亡代理
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            iBookManager = null;
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        tv = findViewById(R.id.tv_book);
        Button acceptData = findViewById(R.id.btn);
        bindService();
        acceptData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Random random = new Random();
                Book book = new Book(random.nextInt(5), "yif" + random.nextInt(3));
                try {
                    iBookManager.add(book);
                    tv.setText(iBookManager.getBookList().toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

Binder设置死亡代理

给Binder设置死亡代理,当服务端异常终止时,客户端收到通知,进行重新绑定远程服务,首先声明DeathRecipient对象,它是一个接口 这样就会回调binderDied对象,移除之前绑定的binder代理,并重新绑定远程服务。

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (iBookManager == null) {
                return;
            }
            //flag为标记位
            iBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            iBookManager = null;
            //重新绑定远程服务
            bindService();
        }
    };
 
    private void bindService() {
        Intent intent = new Intent(this, BinderService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }
}

RemoteCallbackList接口

/** 
    * 
    * RemoteCallbackList是系统专门用来删除跨进程listener接口 
    * 
    * /
private RemoteCallbackList<IONewBookArrivedListener> mListener = new RemoteCallbackList<>();
@Override        
public void registerListener(IONewBookArrivedListener listener) throws RemoteException {            
mListener.register(listener);        
}        
@Override        
public void unregisterListener(IONewBookArrivedListener listener) throws RemoteException {            
mListener.unregister(listener);        
}
private void onNewBookArrived(Book book)throws RemoteException{    mBookList.add(book);//必须配对使用    
final int N = mListener.beginBroadcast();    
for(int i=0;i<N;i++){        
IONewBookArrivedListener listener = mListener.getBroadcastItem(i);        Log.i(TAG, "onNewBookArrived: notify listener");        
//当服务端有新书来,调用该方法,将新书对象通过参数传递给客户端        listener.onNewBookArrived(book);    
}    
mListener.finishBroadcast();
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 遍历取出Map集合key-value数据的4种方法

    知识补充: list和set集合都实现了Iterable接口,所以他们的实现类可以使用迭代器遍历,map集合未实现该接口,若要使用迭代器循环遍历,需要借助se...

    似水的流年
  • 修复Long类型太长,而Java序列化JSON丢失精度问题的方法

    Java序列化JSON时long型数值,会出现精度丢失的问题。  原因:  java中得long能表示的范围比js中number大,也就意味着部分数值在js中存...

    似水的流年
  • 【玩转SpringBoot】异步任务执行与其线程池配置

    同步代码写起来简单,但就是怕遇到耗时操作,会影响效率和吞吐量。 此时异步代码才是王者,但涉及多线程和线程池,以及异步结果的获取,写起来颇为麻烦。 不过在遇到...

    Java3y
  • Spring读取mybatis在多个jar包下的的mapper文件

    刚开始的时候我的配置文件在同名目录下都是在/mapper下,导致只能读取一个jar中的mapper文件。先解决如下: 1.将mapper文件放在不能放在同名的目...

    似水的流年
  • XMind 8 安装激活

    首先去xmind国外官网下载对应操作系统的安装包,国内官网的那个是有残缺的,不支持破解。

    王小明_HIT
  • Spring的统一事务模型

    Spring事务包含对分布式事务和单机事务的支持,我们用的比较多的是单机事务,也就是只操作一个数据库的事务。

    Java3y
  • swagger报错This application has no explicit mapping for /

    是因为swagger-ui.html 是在springfox-swagger-ui.jar里的,因为修改了路径Spring Boot不会自动把/swagger-...

    似水的流年
  • 图解一道腾讯笔试算法题:「最长上升子序列」

    今天分享的题目来源于 LeetCode 第 300 号问题:最长上升子序列。这道题在 腾讯 笔试中出现过 3 次。

    五分钟学算法
  • Java并发基础,不怕你看不懂!

    当我们使用计算机时,可以同时做许多事情,例如一边打游戏一边听音乐。这是因为操作系统支持并发任务,从而使得这些工作得以同时进行。

    Java3y
  • 一文道尽JavaScript 20年的发展史

    在过去的几个月里,我通过Node 8,Webpack 4和Babel 7提供的本地工具学到了很多关于现代JavaScript和CSS开发的知识。作为其中的一部分...

    五月君

扫码关注云+社区

领取腾讯云代金券