Android Binder机制浅谈以及使用Binder进行跨进程通信的俩种方式(AIDL以及直接利用Binder的transact方法实现)

Binder机制学习

Binder机制是Android进行IPC(进程间通信)的主要方式

Binder跨进程通信机制:基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。 进程空间分为用户空间和内核空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用 一个内核空间 Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;

·为何新增Binder来作为主要的IPC方式

Android也是基于Linux内核,Linux现有的进程通信手段有管道/消息队列/共享内存/套接字/信号量。

既然有现有的IPC方式,为什么重新设计一套Binder机制呢?

主要是出于以上三个方面的考量:

1、效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度 分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷 贝到接收方的缓存区,一共两次拷贝。

一次数据传递需要经历:用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,这样效率不高。 

而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同 一块物理地址的,节省了一次数据拷贝的过程 : 共享内存不需要拷贝,Binder的性能仅次于共享内存。 
2、稳定性:上面说到共享内存的性能优于Binder,那为什么不采用共享内存呢,因为共享内存需要处理并发同 步问题,容易出现死锁和资源竞争,稳定性较差。 Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较 好。
3、安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制 为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。

2. binder是什么?

img

从进程间通信的角度看,Binder 是一种进程间通信的机制;

从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象(Binder类 IBinder);

从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理

从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会自动完成代理对象和本地对象之间 的转换。 从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁

3.Binder 跨进程通信机制 模型

·模型原理图:Binder 跨进程通信机制 模型 基于 Client - Server 模式

img

· 模型组成角色说明

img

4.Binder驱动的作用 & 原理:

img

模型工作原理:

img

模型原理步骤说明

img

注意:

Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互

原因:Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互
Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)

所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程 并 显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信
# Binder请求的线程管理
Server进程会创建很多线程来处理Binder请求

Binder模型的线程管理 采用Binder驱动的线程池,并由Binder驱动自身进行管理,而不是由Server进程来管理的

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。

所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作

利用binder进行通信的实现代码Demo:

1. 直接利用Binder的transact实现:

server端:(注意,要在注册清单中注册,并且标注进程号(在其他进程中运行))

public class IPCService extends Service {
     
     
private static final String DESCRIPTOR = "IPCService";
private final String[] names = {
     
     "B神","艹神","基神","J神","翔神"};
private MyBinder mBinder = new MyBinder();
private class MyBinder extends Binder {
     
     
  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
     
     
      switch (code){
     
     
          case 0x001: {
     
     
              Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid());
              data.enforceInterface( "IPCService");
              int num = data.readInt();
              int num1 = data.readInt();
              int num2 = data.readInt();
              String test = data.readString();
              reply.writeNoException();
              reply.writeString(names[num] + "  " + android.os.Process.myPid() + "    " + num1 + "   " + num2 + "   " + test);
              reply.writeInt(1);
              reply.writeString("收到");
              return true;
          }
      }
      Log.d("TAG", "MyBinder   OnTransact块 ----- " + android.os.Process.myPid());
      return super.onTransact(code, data, reply, flags);
  }
}
@Override
public IBinder onBind(Intent intent) {
     
     
  return mBinder;
}
}
/**
 * AndroidManifest.xml
         <service android:name="net.binderlearning.IPCService"
                 android:process=".myservice"/>
*/

client:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     
     
    private EditText edit_num;
    private Button btn_query;
    private TextView txt_result;
    private IBinder mIBinder;
    private ServiceConnection PersonConnection  = new ServiceConnection()
    {
     
     
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
     
     
            mIBinder = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
     
     
            mIBinder =  service;
            Log.d("TAG", "客户端-----" + android.os.Process.myPid());
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //绑定远程Service
        Intent service = new Intent(this,IPCService.class);
        bindService(service, PersonConnection, BIND_AUTO_CREATE);
        btn_query.setOnClickListener(this);
    }
    private void bindViews() {
     
     
        edit_num = (EditText) findViewById(R.id.edit_num);
        btn_query = (Button) findViewById(R.id.btn_query);
        txt_result = (TextView) findViewById(R.id.txt_result);
    }
    @Override
    public void onClick(View v) {
     
     
        int num = Integer.parseInt(edit_num.getText().toString());
        if (mIBinder == null)
        {
     
     
            Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
        } else {
     
     
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            String _result = null;
            try{
     
     
                _data.writeInterfaceToken("IPCService");
                _data.writeInt(num);
                _data.writeInt(4);
                mIBinder.transact(0x001, _data, _reply, 0);
                _reply.readException(); //读取异常
                _result = _reply.readString();
                String test = _reply.readString();
                int test1 = _reply.readInt();
                Toast.makeText(this, "我收到的内容是:" + test + "    " + test1, Toast.LENGTH_SHORT).show();
                txt_result.setText(_result);
                edit_num.setText("");
                Log.d("TAG", "客户端-----" + android.os.Process.myPid());
            }catch (RemoteException e)
            {
     
     
                e.printStackTrace();
            } finally
            {
     
     
                _reply.recycle();
                _data.recycle();
            }
        }
    }
}

运行结果:

在这里插入图片描述

在这里插入图片描述

方法说明及其注意点:

1. Parcel的读写顺序要一致。 比如写的时候先 writeInt ,然后再writeString。 那么读的时候也是要先readInt 然后再writeString。 (原因应该是跟Parcelable差不多,调用的是native层的序列化写入,用的是c/c++的指针顺序写入,如果没有按顺序读取,读取地址的时候读到的内容就会很奇怪了)

例如,服务端写入顺序:

在这里插入图片描述

客户端读取顺序:

在这里插入图片描述

结果:

在这里插入图片描述

收到的数据不理想

2. Parcel的writeInterfaceToken(接口名)以及enforceInterface(接口名)

·writeInterfaceToken以及writeInterfaceToken的接口名要一致(类似于验证要访问的接口是否一致)。

不一致,如果找不到binder找不到要调用的接口,就会报异常

java.lang.SecurityException: Binder invocation to an incorrect interface

例如:服务端

在这里插入图片描述

客户端:

在这里插入图片描述

报错:

在这里插入图片描述

·writeInterfaceToken以及writeInterfaceToken的调用时期必须在读写数据前调用,否则会报错:

java.lang.SecurityException: Binder invocation to an incorrect interface

例如:在写入数据后才调用时:

在这里插入图片描述

结果报错:

在这里插入图片描述

源码对这俩个函数的描述:

    /**
     * Store or read an IBinder interface token in the parcel at the current
     * {@link #dataPosition}. This is used to validate that the marshalled
     * transaction is intended for the target interface. This is typically written
     * at the beginning of transactions as a header.
     * 翻译:
        在包中当前数据位置存储或读取IBinder接口令牌。这用于验证编组的事务是否用于目标接口。这通常在事务开始时作为标头写入。
     */

    public final void writeInterfaceToken(@NonNull String interfaceName) {
     
     
        nativeWriteInterfaceToken(mNativePtr, interfaceName);
    }
    /**
     * Read the header written by writeInterfaceToken and verify it matches
     * the interface name in question. If the wrong interface type is present,
     * {@link SecurityException} is thrown. When used over binder, this exception
     * should propagate to the caller.
     * 翻译:
     读取writeInterfaceToken所写的报头,并验证它是否与所讨论的接口名称匹配。如果存在错误的接口类型,则抛出SecurityException。在绑定器上使用时,此异常应传播给调用方。
     */
    public final void enforceInterface(@NonNull String interfaceName) {
     
     
        nativeEnforceInterface(mNativePtr, interfaceName);
    }

3.Parcel的readException()

作用是读取异常,如果读写的时候有异常,那么就能获取到改异常(获取异常不catch就会导致程序奔溃)。 上述代码可以选择不加这个,不加的话如果有异常就获取不到,也不会导致程序奔溃,但读不到数据

在这里插入图片描述

4. transact和onTransact

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)

参数说明: 1.code,服务端和客户端约定的标识码,这样在服务端中就可以根据改标识码来处理不同的事件

​ 2.data, 用于读取和写入 要通信的数据

​ 3.reply,用于读取和写入 返回的数据 4. flags 可以不管

transact在客户端中调用,ontransact在服务端中调用。 (当然一个进程可以同时为服务端和客户端。 也就是binder机制支持"递归化调用",比如A跟B通信,A可以调用B提供的Ibinder对象的transact跟B通信,同时B也可以调用A提供的IBinder对象的transact跟A通信。 然后他们在各自的onTransact方法中处理即可)


上面的Demo只是验证了通信,并没有真正意义上的调用另一个进程的接口方法,因此再测试了一个Demo调用另外一个进程的方法:

定义接口方法:一定要继承IInterface

import android.os.IInterface;

public interface IPlus extends IInterface {
     
     
      int add(int a, int b);
}

服务端代码:

public class IPCService extends Service {
     
     
    private static final String DESCRIPTOR = "add two int";
    private final String[] names = {
     
     "B神","艹神","基神","J神","翔神"};
    private MyBinder mBinder = new MyBinder();

    private IInterface plus = new IPlus() {
     
     
        @Override
        public int add(int a, int b) {
     
     
            return a + b;
        }

        @Override
        public IBinder asBinder() {
     
     
            return null;
        }
    };
    public IPCService(){
     
     
        mBinder.attachInterface(plus,"add two int");
    }
    private class MyBinder extends Binder {
     
     
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
     
     
            switch (code){
     
     
                case 0x001: {
     
     
                    Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid());
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b);
                    reply.writeNoException();
                    reply.writeInt(result);
                    return true;
                }
            }
            Log.d("TAG", "MyBinder   OnTransact块 ----- " + android.os.Process.myPid());
            return super.onTransact(code, data, reply, flags);
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
     
     
        return mBinder;
    }
}

客户端代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     
     
    private EditText edt_arg1;
    private EditText edt_arg2;
    private Button add;
    private TextView addResult;
    private IBinder mIBinder;
    private ServiceConnection PersonConnection  = new ServiceConnection()
    {
     
     
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
     
     
            mIBinder = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
     
     
            mIBinder =  service;
            Log.d("TAG", "客户端-----" + android.os.Process.myPid());
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //绑定远程Service
        Intent service = new Intent(this,IPCService.class);
        bindService(service, PersonConnection, BIND_AUTO_CREATE);
        add.setOnClickListener(this);
    }
    private void bindViews() {
     
     
        edt_arg1 = (EditText) findViewById(R.id.arg1);
        edt_arg2 = (EditText) findViewById(R.id.arg2);
        add = (Button) findViewById(R.id.add);
        addResult = (TextView) findViewById(R.id.result);
    }
    @Override
    public void onClick(View v) {
     
     
        int arg1 = Integer.parseInt(edt_arg1.getText().toString());
        int arg2 = Integer.parseInt(edt_arg2.getText().toString());
        if (mIBinder == null)
        {
     
     
            Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
        } else {
     
     
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result = -1;
            try{
     
     
                _data.writeInterfaceToken("add two int");
                _data.writeInt(arg1);
                _data.writeInt(arg2);
                mIBinder.transact(0x001, _data, _reply, 0);
                _reply.readException();
                _result  = _reply.readInt();
                addResult.setText(""+_result );
            }catch (RemoteException e)
            {
     
     
                e.printStackTrace();
            } finally
            {
     
     
                _reply.recycle();
                _data.recycle();
            }
        }
    }
}

运行结果:

在这里插入图片描述

区别第一个Demo:

这里多使用了IInterface,即IPlus

并且在服务端的代码里多写了:

    public IPCService(){
     
     
        mBinder.attachInterface(plus,"add two int");
    }
    /**
     *   void attachInterface(IInterface plus, String descriptor);
          // 作用:
          // 1. 将(descriptor,plus)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中
          // 2. 之后,Binder对象 可根据descriptor通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用
    *      
    */
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b);

/**
 *         IInterface queryLocalInterface(Stringdescriptor) ;
        作用:根据 参数 descriptor 查找相应的IInterface对象(即plus引用)
**/

Binder跨进程的通信AIDL方案:

AIDL使用步骤:

  1. 创建AIDL文件

在这里插入图片描述

  1. 写上服务端需要提供的接口方法

在这里插入图片描述

  1. 重新build一下模块,就会自动生成aidl文件对应的java文件

测试Demo1:(同一模块下时)

AIDL文件代码:

interface IMyAidlInterface {
     
     
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);  //默认生成,可以删除

    //以下俩个是自定义方法
    void say(String word);
    int tell(String word,int age);
}

服务端代码:

public class MyAidlServer extends Service {
     
     
    private String TAG = "MyAidlService";
    //使用生成的java文件中的stub作为binder对象
    private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
     
     
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
     
     
            Log.d(TAG, android.os.Process.myPid() + "   basicTypes:  " + anInt + "   " + aLong + "   " + aBoolean + "   "
                  + aFloat + "   " + aDouble + "   " + aString);
        }

        @Override
        public void say(String word) throws RemoteException {
     
     
            Log.d(TAG, android.os.Process.myPid() + "   say:  "  + word);
        }

        @Override
        public int tell(String word, int age) throws RemoteException {
     
     
            Log.d(TAG, android.os.Process.myPid() +  "   tell:   " + word + "    " + age);
            return 100;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
     
     
        return stub;
    }
}

客户端代码:

public class MainActivity extends AppCompatActivity {
     
     
    private static final  String TAG = "MainActivity";
    TextView bindService;
    ServiceConnection serviceConnection = new ServiceConnection() {
     
     
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
     
     
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); //获取到服务端的binder代理对象,也就是上面stub
            try {
     
     
                iMyAidlInterface.say("I am handsome boy");
                int result = iMyAidlInterface.tell("我是个靓仔", 20);
                Log.d(TAG, android.os.Process.myPid() +"    " + "onServiceConnected:   " + result);
            } catch (RemoteException e) {
     
     
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
     
     

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService = findViewById(R.id.bind_service);
        bindService.setOnClickListener((v) -> {
     
     
            Intent intent = new Intent(MainActivity.this, MyAidlServer.class);
            bindService(intent,serviceConnection,BIND_AUTO_CREATE); //绑定服务
        });
    }
}

结果:不同进程的日志需要指令抓取才可以显示出来

客户端进程:

在这里插入图片描述

服务端进程:

在这里插入图片描述

测试Demo2:(不同模块时)

服务端aidl:

interface IServiceAidlInterface {
     
     
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void setData(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString,byte anByte);   //aidl不支持short类型,但可以考虑把short转换成int解决
    void saySomething(String word);
    String saySomethingAndRespon(String word);
}

服务端Service:

public class AidlService extends Service {
     
     
    private String TAG = "AidlService";
    private IServiceAidlInterface.Stub stub = new IServiceAidlInterface.Stub() {
     
     
        @Override
        public void setData(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString, byte anByte) throws RemoteException {
     
     
            Log.d(TAG,android.os.Process.myPid() +  "   服务端收到客户端调用setData传入的数据为:  " + anInt + "   " + aLong + "   "
            + aBoolean + "   " + aFloat + "    " + aDouble + "    " + aString + "     " + (int) anByte);
        }

        @Override
        public void saySomething(String word) throws RemoteException {
     
     
            Log.d(TAG, android.os.Process.myPid() + "    服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word);
        }

        @Override
        public String saySomethingAndRespon(String word) throws RemoteException {
     
     
            Log.d(TAG, android.os.Process.myPid() + "    服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word);
            return "我是服务端,我已经接收到你发送过来的数据了。 ---> " + word;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
     
     
        return stub;
    }

    @Override
    public boolean onUnbind(Intent intent) {
     
     
        Log.d(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
     
     
        super.onDestroy();
        Log.d(TAG, "onDestory: ");
    }
}

服务端的注册清单: 此处只为注册service。 且exported属性为true是一定要的

        <service android:name="net.aidl_server.AidlService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

客户端:aidl文件直接从服务端复制过来(自建 包名会跟着客户端)

绑定服务的代码:

public class MainActivity extends AppCompatActivity {
     
     
    private final String TAG = "ClientMainActivity";
    Button bind_service;
    Intent intent;
    private ServiceConnection connection = new ServiceConnection() {
     
     
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
     
     
            IServiceAidlInterface aidlInterface = IServiceAidlInterface.Stub.asInterface(service);
            try {
     
     
                aidlInterface.setData(10,100L,true,50.01f,79.0,"XIAO JIAN", (byte) 90);
                aidlInterface.saySomething("服务端你好呀");
                String respon = aidlInterface.saySomethingAndRespon("你是个靓仔");
                Log.d(TAG, android.os.Process.myPid()+"      我是客户端,收到服务端传送过来的消息为: " + respon);
            } catch (RemoteException e) {
     
     
                throw new RuntimeException(e);
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
     
     

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bind_service = findViewById(R.id.bind_service);
        bind_service.setOnClickListener((v)->{
     
     
            intent = new Intent("android.intent.action.AIDLService");
            intent.setPackage("net.aidl_server");
            bindService(intent,connection,BIND_AUTO_CREATE);
        });

    }

    @Override
    protected void onDestroy() {
     
     
        super.onDestroy();
        Log.d(TAG, "onDestroy: " );
    }


}

运行结果:

服务端

在这里插入图片描述

客户端:

在这里插入图片描述

Aidl传输复杂数据注意事项:

数据需要实现Parceable接口,并且格式要跟Parceable实现格式一样,可以把鼠标指在Parceable那,然后Alt + 回车

在这里插入图片描述

需要在aidl文件中加入Parceable实现类

在这里插入图片描述

定义接口参数需要加上in

在这里插入图片描述

在AIDL中,in、out和inout是定向标记,用于指示数据在跨进程通信中的流向。其中,
in表示数据只能从客户端流向服务端,
out表示数据只能从服务端流向客户端,
inout表示数据可以在服务端和客户端之间双向流动
需要注意的是,in 和 out 的作用只是为了告诉系统参数的传递方向,实际上在 AIDL 中,所有的参数都是通过值传递的,即传递的是参数的副本,而不是参数本身。因此,如果需要修改参数的值,需要使用 inout 或者返回值的方式来实现。

引用和参考

Carson带你学Android–Android跨进程通信:图文详解 Binder机制 原理

猜你喜欢

转载自blog.csdn.net/XJ200012/article/details/131204391