参考

轻松理解 Android Binder,只需要读这一篇

图文详解 Android Binder跨进程通信的原理

Android中的Parcel是什么

Android Binder IPC通信机制对比

前言

Android中有很多种IPC通信机制,如共享内存、管道、socket等。

Linux中的内存管理中存在虚拟内存和物理内存两种概念;每个进程都拥有自己独立的虚拟内存空间,这样用户进程可以认为自己所有用的是一段独立、连续的内存空间,而不必管这些数据具体存在于物理内存的哪个段、哪个页上,虚拟内存的映射统一交给kernel去管理。

Android中每个进程可以访问到的虚拟内存空间分为用户空间和内核空间。其中用户空间为每个进程独占,一个进程无法访问到其他进程的用户空间;而内核空间是公用的。所以大多数传统IPC采用的都是以公用的内核空间为数据传递的中间站,通过

copy_from_user():将用户空间的数据拷贝到内核空间;

copy_to_user():将内核空间的数据拷贝到用户空间;

完成数据由进程A用户空间到进程B用户空间的传递。

也就是说,在传统的IPC中,数据需要两次的拷贝过程才可以完成进程间通信。

再看下共享内存,简单来说,共享内存是依赖与内存映射来完成的。A和B两个进程使用共享内存进行通信,A和B将自己虚拟空间与共享对象进行内存映射。共享内存无需数据拷贝就可以完成数据通信,但同时,由于共享内存方法在使用上比较繁琐,再加上在一些使用场景不是很适用(比如A进程想使用B进程提供的某个服务,并获得这个服务的执行结果),所以此时就需要引入Android原生的IPC通信方式Binder,但共享内存在某些使用场景如大量数据的传输中,契合度还是很高的。

先看下Android几种IPC方式,数据拷贝次数的对比:

IPC

数据拷贝次数

共享内存

0

Binder

1

Socket/管道/消息队列

2

白话浅析Binder

Binder由Client进程、Server进程、Binder驱动、ServerManager组成。

举个例子,甲和乙两个人使用QQ聊天,甲问乙一句明天天气怎么样,乙回了一句明天下午,甲接受到了这条回复。甲和乙就相当于两个进程,他们因为不在一个地方所以无法直接进行通信,所以要借助IM工具、硬件、网络来完成这一系列交互。首先甲和乙要通过QQ聊天,那么乙一定是要注册QQ并在线的,那么此时甲相当于client进程,乙相当于server进程,乙注册QQ上线后,相当于告诉了QQ服务端自己的用户名、用户id、ip和端口号,并建立连接。那么这时候甲将信息发到QQ服务端后,QQ后端服务会根据数据库中存放的表,通过甲发送过来的乙的用户id来找到和乙建立的链接,然后将消息发送给乙。在这个过程中,QQ后端服务分管注册、用户查询、路由的这部分功能就相当于ServerManager,而双方的PC、QQ软件、网路、QQ后端服务的其他功能就相当于Binder Driver。

当然,拿QQ聊天举例子只是为了对Binder到底是个什么东西有个初步的认识,其中的实现细节会有很大的不同。

结合之前所说的,传统IPC通信是通过数据由用户空间拷贝到内核空间,再拷贝到用户空间完成的。共享内存是通过用户空间对共享对象的映射完成的。那么Binder呢?

下面盗图一张,图片出处 图文详解 Android Binder跨进程通信的原理

可以看到,client进程首先将要发送的数据通过Binder驱动,拷贝到内核空间,然后这个内核空间其实是与Binder创建的接收缓存区以及Server进程的用户空间是映射关系的,这样Client向内核空间copy数据后,相当于直接写到了server进程的用户空间,而server进程在接受到数据后并进行一些列的逻辑处理后,将结果写入到自己的用户空间,由于内存映射,也同时相当于写入到了Client进程的内核空间,然后再将返回的结果从内核空间拷贝到用户空间就可以了。也就是说单向的数据传递,在建立好了内存映射后,只需要发生一次数据拷贝即可。

从这可以看出,Binder对比与传统的IPC方式,数据传输效率是有提升的,而由于管道和socket等方式需要将自己的管道id和端口号等信息暴漏出来,而Binder则将所有注册和路由的方式交给ServerManager和Binder驱动来做,所以在安全性上,binder对比传统IPC方式也有很大提升。

从代码角度来看如何使用Binder

Binder Driver和ServerManager是由Android 的FW和HAL来实现的,那么做为应用开发人员使用Binder的重点就是就是实现client和server进程。

还是以上面甲询问乙天气怎么样为例,首先乙作为server进程,要定义好自己可以提供的服务,也就是说现在乙现在只能回答甲天气情况,而没回答吃饭了没~然后要将自己注册到ServerManager上。

那Server进程如何将自己注册到ServerManager上,并实现接受和发送的能力呢?这些都已经被封装好了,Server进程只需要继承Binder这个类,并且重写其中的

onTransact(int code, Parcel data, Parcel reply, int flags)

这个方法,并在实现onBind这个抽象方法,并将刚才继承Binder的那个类的对象通过onBind方法返回就可以了。

上代码

package com.qyy.remotemonitor.ui.service;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.annotation.Nullable;import java.util.Date;/*** Created by qinyy on 1/23/2019.*/public class MyServerService extends Service
{public static final int WEATHER_INFO = 999;private MyServer mMyServer = new MyServer();private class MyServer extends Binder implements IMyServer{@Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException{switch (code){case WEATHER_INFO:data.enforceInterface("MyServer");long timeStamp = data.readLong();String result = getWeatherInfo(com.blankj.utilcode.util.TimeUtils.millis2Date(timeStamp));reply.writeInterfaceToken("MyClient");reply.writeString(result);return true;}return super.onTransact(code, data, reply, flags);}@Override public String getWeatherInfo(Date date){String weather = "";//根据date查询天气情况// mPresenter.getWeather(date);return weather;}}@Nullable @Override public IBinder onBind(Intent intent){return mMyServer;}public interface IMyServer{String getWeatherInfo(Date date);}
}

总结一下编写Server进程的要点:

1. 编写一个继承Binder类的类。

2. 重写Binder类中的onTransact方法,这个方法是为了接受Client传来的参数并在这里实现业务处理,并将结果返回给Client。这个方法是运行在Server进程利用进程池创建的线程中的,这个线程池的最大容量是16.

3. 通信使用了Parcel进行数据的封装与序列化,如果只传递简单的参数直接read就可以,如果需要传递复杂的参数,如何封装Parcel和读取,请参考Android中的Parcel是什么。

4. enforceInterface和writeInterfaceToken是配套出现的,用来在通信双端进行一个校验,writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必要的,因为客户端既然已将获取指定远程服务的Binder引用,那么就不会调用其他远程服务,该名称作为Binder驱动确保客户端的确像调用指定的服务端,所以要在服务端在接受的时候调用enforeInterface来校验,至于校验的值,只要通信双方约定好即可。

5. 在Server进程的服务中,实现Service的onBind抽象方法,并将上面集成Binder的类实例化的对象返回。那这个Binder对象就相当于注册在Binder驱动中的一个remote代理,可以通过这个对象实现对Server进程的通信。

再看一下client进程如何通过Binder调用Server进程提供的服务,先看代码:

public class MyClientActivity extends Activity
{IBinder mRemoteService;ServiceConnection mServiceConnection = new ServiceConnection(){@Override public void onServiceConnected(ComponentName componentName, IBinder iBinder){mRemoteService = iBinder;getWeatherRemote();}@Override public void onServiceDisconnected(ComponentName componentName){mRemoteService = null;}};private void getWeatherRemote(){Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeInterfaceToken("MyServer");//查询今天的天气情况data.writeLong(TimeUtils.getNowMills());try{mRemoteService.transact(MyServerService.WEATHER_INFO, data, reply, 0);reply.enforceInterface("MyClient");String result = reply.readString();ToastUtils.showLong("今天天气:" + result);}catch (RemoteException e){e.printStackTrace();}finally{data.recycle();reply.recycle();}}@Override protected void onCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);Intent intent = new Intent(this,MyServerService.class);bindService(intent,mServiceConnection,BIND_AUTO_CREATE);}
}

1. 首先,声明一个IBinder对象,这个对象在client来看就是server的代理,可以通过它与server通信。

2. 定义一个ServiceConnection对象,onServiceConnected和onServiceDisconnected两个方法,并在这两个方法中对刚才声明的IBinder对象进行赋值和置空。

3.使用BindService方法绑定Server进程的Service,并在参数中传入上一步定义的ServiceConnection对象,这样就可以完成由客户端到服务端的链接了。

4. client向server发送消息调用Server提供的服务,其实就是对parcel对象进行封装并传入约定值的过程,和server端接受数据并返回很像,可以直接看下代码。

5. client向server发送信息后,其实在Binder驱动中,会将client进程中执行发送数据的这个线程挂起来,然后在线程池中启动一个线程处理server端的接受逻辑,当Server端返回数据后,会notify被阻塞的client线程。

这样一个基本的Binder通信就完成了,但是如果是一些复杂数据传递起来,我们在封装parcel和解析的时候会很麻烦,还好Android对Binder的使用进行了一次封装,既AIDL。

使用AIDL实现两个应用的通信

AIDL最常用的使用场景就是两个应用之间的通信。要使用AIDL,首先要在两个应用的工程中分别建立aidl文件,注意这两个文件的包名和内容要相同,使用as建立aidl的方法如下:

创建完成后aidl文件所在的目录,这个包名是可以更改的。

看下现在里面是啥都没有的,只有一个自动生成的方法:

// IMyAidlInterface.aidl
package com.qyy.myaidl;// Declare any non-default types here with import statementsinterface 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);
}

先不用管这个方法,我们可以修改这个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);String getWeatherInfo(long timestamp);
}

这样AIDL文件就定义好了,如果是应用间通信,别忘了这两个应用每个都要有一份这个文件。

使用AIDL通信同样有server端和client端的区别,老规矩,还是先看server端的实现。

package com.qyy.remotemonitor.ui.service;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;import com.qyy.myaidl.IMyAidlInterface;/*** Created by qinyy on 1/23/2019.*/public class MyAIDLService extends Service
{private IMyAidlInterface.Stub mIMyAidlInterface = new IMyAidlInterface.Stub(){@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString) throws RemoteException{}@Override public String getWeatherInfo(long timestamp) throws RemoteException{String weather = "";//根据date查询天气情况// mPresenter.getWeather( com.blankj.utilcode.util.TimeUtils.millis2Date(timestamp));return weather;}};@Nullable @Override public IBinder onBind(Intent intent){return mIMyAidlInterface;}
}

对比一下,和上面使用Binder写的server端是不是很像,但是更加的简洁?

可以看到,我们只要创建一个AIDL自动生成的stub对象,并将我们在aidl中定义的接口给实现,再把这个stub对象在onBind中返回就好了。而onTransact的重写、parcel的解析什么的,aidl都帮我们做好了~ 通过查看源码可以发现,这个stub对象,就是AIDL根据我们写的AIDL文件自动生成的、继承了IBinder的一个类,它已经重写了onTransact并在里面实现了parcel的解析、服务请求码的分发、向client返回结果等等操作,使用起来非常的简单。

回头看下客户端如何实现,client是存在另外一个应用中的,那这个应用中有可能有很多地方都需要server提供的服务,上面写Binder客户端的时候,我们是在bindService的时候传入一个ServiceConnection对象,并重写其中的监听方法,为了可以在client应用全局使用Server提供的服务,可以把上面的步骤写在client应用的application类中。

 private IMyAidlInterface mRemoteService;private ServiceConnection mServiceConnection = new ServiceConnection(){@Override public void onServiceConnected(ComponentName name, IBinder service){mRemoteService = IMyAidlInterface.Stub.asInterface(service);}@Override public void onServiceDisconnected(ComponentName name){mRemoteService = null;}};

声明一个AIDL文件接口的对象,然后和binder的使用一样,定义一个ServiceConnection,并在serviceconnect和disconnect的时候对remoteService进行赋值和置空

然后在合适的时机bind service

Intent remoteIntent = new Intent("服务端Service的包名加服务名");bindService(createExplicitFromImplicitIntent(MyApplication.this, remoteIntent),mServiceConnection, BIND_AUTO_CREATE);

createExplicitFromImplicitIntent方法是为了防止Service Intent must be explicit这个异常,具体代码如下:

 public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {// Retrieve all services that can match the given intentPackageManager pm = context.getPackageManager();List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);// Make sure only one match was foundif (resolveInfo == null || resolveInfo.size() != 1) {return null;}// Get component info and create ComponentNameResolveInfo serviceInfo = resolveInfo.get(0);String packageName = serviceInfo.serviceInfo.packageName;String className = serviceInfo.serviceInfo.name;ComponentName component = new ComponentName(packageName, className);// Create a new intent. Use the old one for extras and such reuseIntent explicitIntent = new Intent(implicitIntent);// Set the component to be explicitexplicitIntent.setComponent(component);return explicitIntent;}

然后可以在使用的时候就可以直接通过IMyAidlInterface 的实例调用对应Server端提供的方法了

        mRemoteService.getWeatherInfo(System.currentTimeMillis());

这篇文章只是从一种浅析的、便于使用的角度来分析了Binder和AIDL,如果有兴趣的话可以继续研究Binder驱动和ServerManager的源码。

Android Binder机制浅析及AIDL的使用相关推荐

  1. aidl使用_借助 AIDL 理解 Android Binder 机制——Binder 来龙去脉

    AIDL 是 Android Interface Definition Language(Android 接口定义语言)的缩写,它是 Android 进程间通信的接口语言.由于 Android 系统的 ...

  2. Android 进阶8:进程通信之 Binder 机制浅析

    读完本文你将了解: IBinder Binder Binder 通信机制 Binder 驱动 Service Manager Binder 机制跨进程通信流程 Binder 机制的优点 总结 Than ...

  3. 理解Android Binder机制(3/3):Java层

    本文是Android Binder机制解析的第三篇,也是最后一篇文章.本文会讲解Binder Framework Java部分的逻辑. Binder机制分析的前面两篇文章,请移步这里: 理解Andro ...

  4. 理解Android Binder机制(1/3):驱动篇

    Binder的实现是比较复杂的,想要完全弄明白是怎么一回事,并不是一件容易的事情. 这里面牵涉到好几个层次,每一层都有一些模块和机制需要理解.这部分内容预计会分为三篇文章来讲解.本文是第一篇,首先会对 ...

  5. Android Binder机制学习笔记

    声明,学习材料:http://my.unix-center.net/~Simon_fu/?p=875,不是简单的copy 1    Android的进程间通讯机制(IPC)用的是自己的binder机制 ...

  6. Android - Binder机制 - Binder框架总结

    以下几篇文章是较深入分析binder机制. 目录 1. Android - Binder机制 - ServiceManager 2. Android - Binder机制 - 普通service注册 ...

  7. 由浅入深 学习 Android Binder(一)- AIDL

    Android Binder系列文章: 由浅入深 学习 Android Binder(一)- AIDL 由浅入深 学习 Android Binder(二)- bindService流程 由浅入深 学习 ...

  8. Android Binder机制情景源码分析之Binder回调注册和反注册

    我们在日常开发中,经常用到Binder来进行跨进程通信,有个比较常见的场景是向服务端注册Binder回调,比如: IActivityManager中有两个成对的方法,Client端向AMS所在的服务端 ...

  9. 从AIDL一窥Android Binder机制

    Binder机制在Android系统中地位毋庸置疑,system_server就通过Binder来实现进程间的通信,从而达到管理.调用一系列系统服务的能力.本文就AIDL来解读一下Binder机制的. ...

最新文章

  1. k8s kubectl run命令使用详解
  2. JAVA中int、String的类型转换(亲测)
  3. P2782 友好城市
  4. DDD实战课--学习笔记
  5. LeetCode 427. 建立四叉树(递归)
  6. 那些不是秘密的微信earning方法
  7. 溢信服务转型之代理商技术培训
  8. 360手机浏览器_扰乱网络传播秩序!搜狗、360等手机浏览器国家网信办纳入首批重点整治范围...
  9. 1t硬盘怎么分区最好_还在用128G Macbook?699元升级1T英睿达SSD
  10. C++名称查找与ADL
  11. Codeforces Round #828 (Div. 3)-赛后总结
  12. 数字统计 题解(c++)
  13. uni-app上传图片到腾讯云
  14. 三国演义人物词频统计-4
  15. 记十月五日寨口大坡徒步
  16. js实现图片3D轮播效果(收藏)
  17. UNR #1 火车管理
  18. DXP_protel2004_原理图设计基础_集成运放原理图设计学习
  19. [DB][mysql]下出现 java.sql.SQLException: Incorrect string value: '\xF4\x80\x8E\xAE\xE8\x83...'
  20. CentOS7-启动网卡-查看IP地址-查看端口-管理端口

热门文章

  1. U盘安装ubuntu18.04 优麒麟18.04 详细过程
  2. 鸿蒙os官网2.0,鸿蒙 OS 2.0正式发布,看看用户体验评价,内附可升级型号名单!...
  3. 网狐大联盟脚本还原数据库
  4. 基于Javaweb的小项目(类似于qqzone)1——设计数据库
  5. linux 创建分区 4t,centos对4T硬盘进行分区
  6. J-Hi线下交流--实况
  7. 小i机器人2019数博会C位展实力 “认知智能”引领行业创新变革
  8. 2021-04-10 粤嵌单片机兴趣课(一)
  9. 预测大盘最准确的指标_通达信指标——大盘预测 (主图)
  10. 一文详解光电容积图 (PPG) 和心电图 (ECG) 基本工作原理