不得不说的androidbinder机制与aidl(代码片段)

涂程 涂程     2023-02-19     791

关键词:

好文推荐
作者:玩转安卓Dev

说起Android的进程间通信,想必大家都会不约而同的想起Android中的Binder机制。而提起Binder,想必也有不少同学会想起初学Android时被Binder和AIDL支配的恐惧感。但是作为一个Android开发者,Binder是我们必须掌握的知识。因为它是构架整个Android大厦的钢筋和混凝土,连接了Android各个系统服务和上层应用。只有了解了Binder机制才能更加深入的理解Android开发和Android Framework。这也是为什么无论是《Android开发艺术探索》还是《深入理解Android内核设计思想》这些进阶类书籍把进程间通信和Binder机制放到靠前章节的原因,它太重要了,重要到整个Android Framework都离不开Binder的身影。

本篇文章我们暂且不去探讨Binder的底层实现,因为就目前而言,笔者掌握的程度也不足以去输出Binder实现原理的的内容。因此,为了不误导大家,这里就来写一写Binder的基础用法以及AIDL。虽然关于Binder和AIDL的基础用法网上的内容比比皆是。但是能把Binder和AIDL写的浅显易懂的文章并不多见。也就导致了很多人觉得Binder跟AIDL的使用都很难。

本篇文章,我将通过我自己的学习思路来带大家认识Binder机制和AIDL。

一、进程间通信

在操作系统中,每个进程都有一块独立的内存空间。为了保证程序的的安全性,操作系统都会有一套严格的安全机制来禁止进程间的非法访问。毕竟,如果你的APP能访问到别的APP的运行空间,或者别的APP可以轻而易举的访问到你APP的运行空间,想象一下你是不是崩溃的心都有了。所以,操作系统层面会对应用进程进行内存隔离,以保证APP的运行安全。但是,很多情况下进程间也是需要相互通信的,例如剪贴板的功能,可以从一个程序中复制信息到另一个程序。这就是进程间通信诞生的背景。

广义的讲,进程间通信(Inter-process communication,简称IPC)是指运行在不同进程中的若干线程间的数据交换。

操作系统中常见的进程间通信方式有共享内存、管道、UDS以及Binder等。关于这些进程间的通信方式本篇文章我们不做深究,了解即可。

  • 共享内存(Shared Memory) 共享内存方式实现进程间通信依靠的是申请一块内存区域,然后将这块内存映射到进程空间中,这样两个进程都可以直接访问这块内存。在进行进程间通信时,两个进程可以利用这块内存空间进行数据交换。通过这样的方式,减少了数据的赋值操作,因此共享内存实现进程间通信在速度上有明显优势。

  • 管道(Pipe) 管道也是操作系统中常见的一种进程间通信方式,Windows系统进程间的通信依赖于此种方式实现。在进行进程间通信时,会在两个进程间建立一根拥有读(read)写(write)功能的管道,一个进程写数据,另一个进程可以读取数据,从而实现进程间通信问题。

  • UDS(UNIX Domain Socket) UDS也被称为IPC Socket,但它有别于network 的Socket。UDS的内部实现不依赖于TCP/IP协议,而是基于本机的“安全可靠操作”实现。UDS这种进程间通信方式在Android中用到的也是比较多的。

  • Binder Binder是Android中独有的一种进程间通信方式。它底层依靠mmap,只需要一次数据拷贝,把一块物理内存同时映射到内核和目标进程的用户空间。

本篇文章,我们重点就是了解如何使用Binder实现进程间通信。Binder仅仅从名字来看就给人一种很神秘的感觉,就因为这个名字可能就会吓走不少初学者。但其实Binder本身并没有很神秘,它仅仅是Android系统提供给开发者的一种进程间通信方式。

而从上述几种进程间通信方式来看,无论是哪种进程间通信,都是需要一个进程提供数据,一个进程获取数据。因此,我们可以把提供数据的一端称为服务端,把获取数据的一端称为客户端。从这个角度来看Binder是不是就有点类似于HTTP协议了?所以,你完全可以把Binder当成是一种HTTP协议,客户端通过Binder来获取服务端的数据。认识到这一点,再看Binder的使用就会简单多了。

二、使用Binder实现进程间通信

使用Binder完成进程间通信其实非常简单。我们举一个查询成绩的例子,服务端提供根据学生姓名查询学生成绩的接口,客户端连接服务端通过学生姓名来查询成绩,而客户端与服务端的媒介就是Binder。

1.服务端的实现

服务端自然是要提供服务的,因此就需要我们开启一个Service等待客户端的连接。关于Android的Service这里就不用多说了吧,我们实现一个GradeService并继承Service,来提供成绩查询接口。代码如下:

public class GradeService extends Service 
    public static final int REQUEST_CODE=1000;
    private final Binder mBinder = new Binder() 
        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException 
            if (code == REQUEST_CODE) 
                String name = data.readString();
                // 根据姓名查询学生成绩并将成绩写入到返回数据
                int studentGrade = getStudentGrade(name);
                if (reply != null)
                    reply.writeInt(studentGrade);
                return true;
            
            return super.onTransact(code, data, reply, flags);
        
        // 根据姓名查询学生成绩
        public int getStudentGrade(String name)          
            return StudentMap.getStudentGrade(name);
        
    ;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return mBinder;
    

因为我们要实现的是跨进程通信,因此,我们将这个服务端的Service设置到远程进程中,在AndroidManifest文件中如下:

<service
    android:name="com.zhpan.sample.binder.server.GradeService"
    android:process=":server">
    <intent-filter>
        <action android:name="android.intent.action.server.gradeservice" />
    </intent-filter>
</service>

就这样,一个远程的,提供成绩查询的服务就完成了。

2.客户端的实现

客户端自然而然的是要连接服务端进程成绩查询。因此,我们在客户端的Activity中绑定GradeService进行成绩查询。代码如下:

public class BinderActivity extends AppCompatActivity 
    // 远程服务的Binder代理
    private IBinder mRemoteBinder;

    private final ServiceConnection mServiceConnection = new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) 
            // 获取远程服务的Binder代理
            mRemoteBinder = iBinder;
        

        @Override
        public void onServiceDisconnected(ComponentName componentName) 
            mRemoteBinder = null;
        
    ;



    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        // 绑定服务
        findViewById(R.id.btn_bind_service).setOnClickListener(view -> bindGradeService());
        // 查询学生成绩
        findViewById(R.id.btn_find_grade).setOnClickListener(view -> getStudentGrade("Anna"));
    
    // 绑定远程服务
    private void bindGradeService() 
        String action = "android.intent.action.server.gradeservice";
        Intent intent = new Intent(action);
        intent.setPackage(getPackageName());
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    
    // 从远程服务查询学生成绩
    private int getStudentGrade(String name) 
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int grade = 0;
        data.writeString(name);
        try 
            if (mRemoteBinder == null) 
                throw new IllegalStateException("Need Bind Remote Server...");
            
            mRemoteBinder.transact(REQUEST_CODE, data, reply, 0);
            grade = reply.readInt();
         catch (RemoteException e) 
            e.printStackTrace();
        
        return grade;
    

客户端的代码就是通过绑定远程服务,然后获取到服务的Binder代理,来查询学生成绩。可见,使用Binder实现进程间通信是非常简单的,可以说简单的有点出乎所料。那我们之前写的AIDL是什么呢?AIDL生成的那一堆是什么玩意儿?我们且往下看。

三、代理模式优化Binder使用

虽然上一章中的代码已经非常简单了,但是还是有可以优化的空间。我们可以通过设计模式来进行优化,让代码更加简洁。

首先需要定义一个接口查询成绩的接口IGradeInterface,代码如下:

public interface IGradeInterface 
    // 查询成绩接口
    int getStudentGrade(String name);

1.服务端代码优化

接着,对服务端的代码进行优化。我们实现一个自定义的GradeBinder,并实现上述接口,代码如下:

public class GradeBinder extends Binder implements IGradeInterface 

    @Override
    public int getStudentGrade(String name) 
        return StudentMap.getStudentGrade(name);
    

    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException 
        if (code == REQUEST_CODE) 
            String name = data.readString();
            int studentGrade = getStudentGrade(name);
            if (reply != null)
                reply.writeInt(studentGrade);
            return true;
        
        return super.onTransact(code, data, reply, flags);
    

上述代码将查询成绩的相关逻辑从Service搬到了GradeBinder中。因此,此时Service中只需要在onBind的时候返回GradeBinder的实例即可。代码如下:

public class GradeService extends Service 

    public static final int REQUEST_CODE=1000;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return new GradeBinder();
    

2.客户端代码优化

客户端优化的思路是在连接到远程服务时候实例化一个代理类,代理类持有Binder,让代理类行使Binder的权利。首先来看代理类的代码实现:

public class BinderProxy implements IGradeInterface 
    // 被代理的Binder
    private final IBinder mBinder;
    // 私有化构造方法
    private BinderProxy(IBinder binder) 
        mBinder = binder;
    

    // 通过Binde读取成绩
    @Override
    public int getStudentGrade(String name) 
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int grade = 0;
        data.writeString(name);
        try 
            if (mBinder == null) 
                throw new IllegalStateException("Need Bind Remote Server...");
            
            mBinder.transact(REQUEST_CODE, data, reply, 0);
            grade = reply.readInt();
         catch (RemoteException e) 
            e.printStackTrace();
        
        return grade;
    
  
    // 实例化Binder代理类的对象
    public static IGradeInterface asInterface(IBinder iBinder) 
        if (iBinder == null) 
            return null;
         
        
        if (iBinder instanceof IGradeInterface) 
            LogUtils.e("当前进程");
            // 如果是同一个进程的请求,则直接返回Binder
            return (IGradeInterface) iBinder;
         else 
            LogUtils.e("远程进程");
            // 如果是跨进程查询则返回Binder的代理对象
            return new BinderProxy(iBinder);
        
    


BinderProxy类的构造方法被设置成了private。同时提供了一个asInterface方法中,这个方法通过判断Binder是不是IGradeInterface类型从而确定是不是跨进程的通信。如果不是跨进程通信,则返回当前这个Binder,否则就返回Binder的这个代理类。

接下来客户端连接上远程服务的时候使用BinderProxy获取Binder或者BinderProxy实例。代码如下:

public class BinderProxyActivity extends BaseViewBindingActivity<ActivityBinderBinding> 
    // 此处可能是BinderProxy也可能是GradeBinder
    private IGradeInterface mBinderProxy;

    private final ServiceConnection mServiceConnection = new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) 
            // 连接服务成功,根据是否跨进程获取BinderProxy或者GradeBinder实例
            mBinderProxy = BinderProxy.asInterface(iBinder);
        

        @Override
        public void onServiceDisconnected(ComponentName componentName) 
            mBinderProxy = null;
        
    ;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        binding.btnBindService.setOnClickListener(view -> bindGradeService());
       // 查询学生成绩点击事件,通过mBinderProxy查询成绩
        binding.btnFindGrade.setOnClickListener(view -> ToastUtils.showShort("Anna grade is " + mBinderProxy.getStudentGrade("Anna")));
    

    // 绑定服务
    private void bindGradeService() 
        String action = "android.intent.action.server.gradeservice";
        Intent intent = new Intent(action);
        intent.setPackage(getPackageName());
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    


可以看到,此时的代码相比第一章的代码整洁了不少。但是,代码写起来似乎还没有第一章中的方便。主要是因为要我们增加一个IGradeInterface接口,还要自定义一个GradeBinder,同时,还需要写代理类的相关代码,感觉非常繁琐。那么有没有办法让代码简洁,写起来还不繁琐呢?答案是肯定的,使用AIDL就可以实现。

四、AIDL

AIDL是Android Interface Description Languaged 简写。用于描写客户端/服务端通信接口的一种描述语言。提起AIDL相信很多人都会觉得头大,定义一个AIDL接口,生成了那么多不知所云的代码,看起来简直就是灾难。先别担心,如果你看懂了第三章的内容,那么其实你已经完全掌握了AIDL。没错,说白了AIDL生成的那一坨代码其实就是我们第三章中写的代码。即AIDL的原理其实就是使用了代理模式对Binder的使用进行了优化,使用AIDL保证了代码的整洁,同时也省去了自己编写繁琐的代理类相关代码。

关于AIDL的使用就非常简单了。

1.创建AIDL接口

首先,在要创建AIDL的目录上右键->New->AIDL->AIDl File 来创建一个AIDL文件,如下图所示:

创建一个名为IGradeService的AIDL文件,并添加一个getStudentGrade的方法。代码如下:

// IGradeService.aidl
package com.zhpan.sample.binder.aidl;

// Declare any non-default types here with import statements

interface IGradeService 
    /**
     * 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);

    int getStudentGrade(String name);

接着Rebuild一下项目后IDE就会自动生成AIDL的代码了。

2.AIDL生成的代码

在项目的build目录下com.zhpan.sample.binder.aidl包中会看到自动生成的一个名为IGradeService的接口,代码如下:

// 这个接口相当于上一章中的IGradeInterface接口
public interface IGradeService extends android.os.IInterface 
  
  ...
  
  // Stub是一个Binder,相当于上一章中的GradeBinder
  public static abstract class Stub extends android.os.Binder
      implements com.zhpan.sample.binder.aidl.IGradeService 
    private static final java.lang.String DESCRIPTOR = "com.zhpan.sample.binder.aidl.IGradeService";

    public Stub() 
      this.attachInterface(this, DESCRIPTOR);
    

    public static IGradeService asInterface(android.os.IBinder obj) 
      
      if ((obj == null)) 
        return null;
      
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof com.zhpan.sample.binder.aidl.IGradeService))) 
        // 如果是当前进程则直接返回当前Binder对象
        return ((com.zhpan.sample.binder.aidl.IGradeService) iin);
      
      // 跨进程则返回Binder的代理对象
      return new com.zhpan.sample.binder.aidl.IGradeService.Stub.Proxy(obj);
    

    @Override public android.os.IBinder asBinder() 
      return this;
    

    @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_basicTypes: 
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0 != data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        
        case TRANSACTION_getStudentGrade: 
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          int _result = this.getStudentGrade(_arg0);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        
        default: 
          return super.onTransact(code, data, reply, flags);
        
      
    
    // Binder的代理类,相当于上一章中的BinderProxy
    private static class Proxy implements com.zhpan.sample.androidbinder机制完全解析

概述之前我写过一篇文章AndroidService全面解析,简单实现了如何通过AIDL实现Service的跨进程通信(IPC),其实是通过Binder机制来实现的,本文我们就重点来看看Binder机制的原理。Binder可以提供系统中任何程序都... 查看详情

androidbinder原理

参考技术A以前看源码经常会看到Binder的东西,比如AMS,ActivityThread等,之前虽然对Binder有所了解,但也是模模糊糊的,这次终于下定决心好好的弄一弄它的原理,揭开它头上的那块面纱。首先,Binder主要是Android跨进程通信的一种... 查看详情

androidbinder之底层原理+上层aidl流程梳理

读文字,还不如看图,这是我自己整理的框架图,可能有的地方不够准确,但对于理解Binder底层原理比较友好,点击放大查看哦。 查看详情

androidbinder之底层原理+上层aidl流程梳理

读文字,还不如看图,这是我自己整理的框架图,可能有的地方不够准确,但对于理解Binder底层原理比较友好,点击放大查看哦。 查看详情

androidbinder之底层原理+上层aidl流程梳理

读文字,还不如看图,这是我自己整理的框架图,可能有的地方不够准确,但对于理解Binder底层原理比较友好,点击放大查看哦。 查看详情

androidbinder之底层原理+上层aidl流程梳理

读文字,还不如看图,这是我自己整理的框架图,可能有的地方不够准确,但对于理解Binder底层原理比较友好,点击放大查看哦。 查看详情

binder机制在aidl中的实现分析(代码片段)

...ger.java以上4个文件是aidl不可缺少的部分,怎么实现就不说了,到处都有例子说明。这里就简单贴一下各个文件的代码:/***Createdbysongjunminon2016/11/14.*支持跨进程传输的数据类型*/publicclassBookimplementsParcelablepublicintbookId;pu... 查看详情

1006小明与隔壁老王之间不得不说的故事

1006:小明与隔壁老王之间不得不说的故事时间限制: 1Sec  内存限制: 128MB提交: 355  解决: 152[提交][状态][讨论版]题目描述有一天,小明想偷吃隔壁老王院子里苹果树上的苹果。 但是,老王家有一条... 查看详情

androidbinder机制,共享内存实现原理

参考技术ABinder机制,共享内存实现原理Android匿名共享内存是基于Linux共享内存的,都是在tmpfs文件系统上新建文件,并将其映射到不同的进程空间,从而达到共享内存的目的,只是,Android在Linux的基础上进行了改造,并借助Binder... 查看详情

不得不说,其实你的性能优化手段已经过时了

10月21日-23日,QCon上海站2021将很快与大家见面。已有12年历史的QCon早已沉淀了科学严谨的议题筛选机制和内容打磨流程,我们有十足的信心给你交付足够前沿、可探索、可实践的技术内容。在众多专题中,我想跟你聊... 查看详情

androidbinder机制介绍

做过Android开发的同学可能有些体会,入门初期,工作内容主要是实现各式各样的UI界面,以及实现应用的业务逻辑。在这个阶段,我们会逐渐熟悉View系统,逐渐学会实现各种各样的界面以及动画效果。再往后,当我们想更深入... 查看详情

androidbinder(可以直接写在项目中的写法)(代码片段)

此篇为大家展示一个写在项目中的写法,native层的service注册和获取服务,加上回调。首先展示一下项目目录 binder通信是基于aidl接口实现的,使用aidl接口可以省略许多不必要的代码。工程的基础mkAndroid.mkinclude$(callall... 查看详情

工具git与gitlab/github/coding/...不得不说的秘密。(代码片段)

   git是什么?Gitisafreeandopensourcedistributedversioncontrolsystemdesignedtohandleeverythingfromsmalltoverylargeprojectswithspeedandefficiency.---源自官网https://git-scm.com/意思是:Git是一个免费的开源分布式 查看详情

binder机制在aidl中的实现分析

本篇主要通过结合已经阅读的Binder机制相关资料(《Android开发艺术探索》和http://weishu.me/2016/01/12/binder-index-for-newer/),通过AIDL来进行Binder机制的初步理解感谢两位作者:任玉刚和WeiShu一一个AIDLDemo的组成部分二通信机制的分析1bindS... 查看详情

基础博弈——威佐夫与尼姆不得不说的那些事(代码片段)

STEP1:首先了解规则与概念  威佐夫博弈(Wythoff‘sgame):有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。  尼姆博弈(NimmGame)... 查看详情

Android Binder建模问题——MusicStore、IMusicStore.aidl、Music、IMusic.aidl

】AndroidBinder建模问题——MusicStore、IMusicStore.aidl、Music、IMusic.aidl【英文标题】:AndroidBindermodelingquestion-MusicStore,IMusicStore.aidl,Music,IMusic.aidl【发布时间】:2010-11-1301:19:21【问题描述】:AndroidBinder文档中有一个关于如何通过Binder接... 查看详情

binder机制在aidl中的实现分析(代码片段)

本篇主要通过结合已经阅读的Binder机制相关资料(《Android开发艺术探索》和http://weishu.me/2016/01/12/binder-index-for-newer/),通过AIDL来进行Binder机制的初步理解感谢两位作者:任玉刚和WeiShu一一个AIDLDemo的组成部分二通信机制的... 查看详情

flink窗口与水位线不得不说的秘密(代码片段)

        众所周知,ApacheFlink是一个框架和分布式处理引擎,用于对无界和有界流进行有状态计算。在我们的这个Flink框架中,自Flink1.12.0正式发布流批一体统一运行之后,我们的实时计算框架真正步入了Flink的... 查看详情