A brief discussion of the Android Binder mechanism and two ways to use Binder for cross-process communication (AIDL and direct use of Binder's transact method)

Binder mechanism learning

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

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

·Why is Binder added as the main IPC method?

Android is also based on the Linux kernel. Linux’s existing process communication methods include pipes/message queues/shared memory/sockets/semaphores.

Since there is an existing IPC method, why redesign a Binder mechanism?

Mainly due to the above three considerations:

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. What is binder?

img

From the perspective of inter-process communication, Binder is an inter-process communication mechanism;

From the perspective of the Server process, Binder refers to the Binder entity object (Binder class IBinder) in the Server;

From the perspective of the Client process, Binder refers to the Binder proxy object and is a remote proxy for the Binder entity object.

From the perspective of the transmission process, Binder is an object that can be transferred across processes; the Binder driver will automatically complete the conversion between proxy objects and local objects. From the perspective of Android Framework, Binder is the bridge between ServiceManager and various Managers and the corresponding ManagerService.

3.Binder cross-process communication mechanism model

·Model schematic diagram: BinderThe cross-process communication mechanism model is based on Client - Serverthe pattern

img

· Description of model composition roles

img

4. Function & principle of Binder driver:

img

How the model works:

img

Model principle steps description

img

Notice:

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个线程同时工作

Demo of implementation code for communication using binder:

1. Directly use Binder’s transact implementation:

Server side: (note that it must be registered in the registration list and marked with the process number (running in other processes))

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();
            }
        }
    }
}

operation result:

Insert image description here

Insert image description here

Method description and points to note:

1. The reading and writing order of Parcel must be consistent. For example, when writing, writeInt first, and then writeString. Then when reading, you must first readInt and then writeString. (The reason should be similar to Parcelable. It calls the serialized writing of the native layer and uses the c/c++ pointer to write sequentially. If it is not read in order, the content read when reading the address will be very large. Strange)

For example, server-side write order:

Insert image description here

Client reading order:

Insert image description here

result:

Insert image description here

The data received is not ideal

2. Parcel’s writeInterfaceToken (interface name) and enforceInterface (interface name)

·The interface names of writeInterfaceToken and writeInterfaceToken must be consistent (similar to verifying whether the interfaces to be accessed are consistent).

Inconsistent, if binder cannot be found and the interface to be called cannot be found, an exception will be reported.

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

For example: server

Insert image description here

Client:

Insert image description here

Error reported:

Insert image description here

·The call period of writeInterfaceToken and writeInterfaceToken must be called before reading and writing data, otherwise an error will be reported:

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

For example: when called after writing data:

Insert image description here

The result is an error:

Insert image description here

The source code describes these two functions:

    /**
     * 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()

The function is to read exceptions. If there is an exception when reading and writing, then the exception can be obtained (if the exception is not caught, the program will crash). You can choose not to add this to the above code. If you don't add it, you won't be able to get it if there is an exception, and it won't cause the program to crash, but you won't be able to read the data.

Insert image description here

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)

Parameter description: 1.code, the identification code agreed between the server and the client, so that the server can handle different events according to the changed identification code.

2.data, used to read and write data to be communicated

​ 3.reply, used to read and write returned data 4. flags can be ignored

transact is called on the client and ontransact is called on the server. (Of course, a process can be a server and a client at the same time. That is to say, the binder mechanism supports "recursive calls". For example, A communicates with B. A can call the transact of the Ibinder object provided by B to communicate with B. At the same time, B can also call A. The transact of the provided IBinder object communicates with A. Then they can be processed in their respective onTransact methods)


The above Demo only verified the communication and did not actually call the interface method of another process. Therefore, we tested another Demo method of calling another process:

Define interface methods: must inherit IInterface

import android.os.IInterface;

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

Server code:

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;
    }
}

Client code:

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();
            }
        }
    }
}

operation result:

Insert image description here

Differences between the first Demo:

IInterface, i.e. IPlus, is mostly used here.

And write more in the server code:

    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 cross-process communication AIDL solution:

AIDL usage steps:

  1. Create AIDL file

Insert image description here

  1. Write the interface methods that the server needs to provide

Insert image description here

  1. Rebuild the module and the java file corresponding to the aidl file will be automatically generated.

Test Demo1: (when under the same module)

AIDL file code:

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);
}

Server code:

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;
    }
}

Client code:

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); //绑定服务
        });
    }
}

Result: The logs of different processes need to be captured with instructions before they can be displayed.

Client process:

Insert image description here

Server process:

Insert image description here

Test Demo2: (when using different modules)

Server 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);
}

Server 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: ");
    }
}

Server-side registration list: This is only for registration services. And it is necessary that the exported attribute is 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>

Client: The aidl file is copied directly from the server (the self-built package name will follow the client)

Code to bind the service:

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: " );
    }


}

operation result:

Server

Insert image description here

Client:

Insert image description here

Things to note when transmitting complex data with Aidl:

The data needs to implement the Parceable interface, and the format must be the same as the Parceable implementation format. You can point the mouse over Parceable, and then Alt + Enter

Insert image description here

You need to add the Parceable implementation class to the aidl file

Insert image description here

To define interface parameters, you need to add in

Insert image description here

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

Quotes and References

Carson takes you to learn Android-Android cross-process communication: detailed explanation of the Binder mechanism principle with pictures and texts

Guess you like

Origin blog.csdn.net/XJ200012/article/details/131204391