文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-08-16 17:00

关注

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);  }}@Overridepublic IBinder onBind(Intent intent) {  return mBinder;}}

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

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

在这里插入图片描述

结果报错:

在这里插入图片描述

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

        public final void writeInterfaceToken(@NonNull String interfaceName) {        nativeWriteInterfaceToken(mNativePtr, interfaceName);    }
        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");    }    
                    data.enforceInterface(DESCRIPTOR);                    int a = data.readInt();                    int b = data.readInt();                    int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b);

Binder跨进程的通信AIDL方案:

AIDL使用步骤:

  1. 创建AIDL文件

在这里插入图片描述

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

在这里插入图片描述

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

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

AIDL文件代码:

interface IMyAidlInterface {        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 {        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是一定要的

                                                                        

客户端: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机制 原理

来源地址:https://blog.csdn.net/XJ200012/article/details/131204391

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯