参考
轻松理解 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由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 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文件,注意这两个文件的包名和内容要相同,使用as建立aidl的方法如下:
创建完成后aidl文件所在的目录,这个包名是可以更改的。
看下现在里面是啥都没有的,只有一个自动生成的方法:
// IMyAidlInterface.aidl
package com.qyy.myaidl;
// Declare any non-default types here with import statements
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);
}
先不用管这个方法,我们可以修改这个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 intent
PackageManager pm = context.getPackageManager();
List resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo 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 reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
然后可以在使用的时候就可以直接通过IMyAidlInterface 的实例调用对应Server端提供的方法了
mRemoteService.getWeatherInfo(System.currentTimeMillis());
这篇文章只是从一种浅析的、便于使用的角度来分析了Binder和AIDL,如果有兴趣的话可以继续研究Binder驱动和ServerManager的源码。