前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Binder 进程通信

Binder 进程通信

作者头像
Yif
发布2019-12-26 14:47:11
8270
发布2019-12-26 14:47:11
举报
文章被收录于专栏:Android 进阶

IPC的原理

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

img
img

每个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 接口,以便序列化/反序列化

代码语言:javascript
复制
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 文件夹的包名一致:

代码语言:javascript
复制
// Book.aidl
package com.ztz.androidhighroad;
// Declare any non-default types here with import statements
//和声明的实体类在一个包类
parcelable Book;

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

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

然后创建接口 aidl 文件

代码语言:javascript
复制
// 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,然后等待构建完成。最后显示生成的文件如下:

代码语言:javascript
复制
/*
 * 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,那么客户端就请求失败,可以利用这个做权限验证,让不希望的进程无法远程调用我们的服务

代码语言:javascript
复制
@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;
}

编写服务端类

代码语言:javascript
复制
/**
 * 服务端类
 * @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中注册服务

代码语言:javascript
复制
<service android:name=".service.BinderService"
    android:process=":remote"/>

编写客户端类

代码语言:javascript
复制
/**
 * 客户端类
 *
 * @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代理,并重新绑定远程服务。

代码语言:javascript
复制
    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接口

代码语言:javascript
复制
/** 
    * 
    * 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();
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年7月17日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • IPC的原理
  • 为什么采用Binder进行通信:
  • 编写AIDL文件
    • 创建 AIDL
      • 编写服务端类
        • 编写客户端类
        • Binder设置死亡代理
        • RemoteCallbackList接口
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档