前言
最近重温了Binder机制,在此把看到的一些心得,写一写。
一、Binder IPC
在这里需要先说明的一点是Client端和Server端以及后面提到的ServiceManager都处于用户空间、Binder驱动处于内核空间。
Binder是进程间通讯的一种方式。进程间的通讯方式有很多,比如Socket、管道。而Socket、管道这些需要数据的两次拷贝(Client把数据从自己的进程空间拷贝到内核空间,然后再从内核空间拷贝到Server端的进程空间,这样Server就能客户端传递的数据,这个过程经历过了两次数据拷贝)。而Binder方式只需要一次数据拷贝:只需要Client把数据拷贝到内核空间,然后将拷贝到内核空间的数据同时映射到Server进程虚拟地址空间和内核虚拟地址空间,这样经过一次数据拷贝,客户端和服务端就能进行通讯了。
注意:并不是所有的Binder服务都会注册到ServiceManager中的,像AMS、WMS、PMS这些系统的Binder服务是会注册到SM中并受SM管理的,而像bindService方式创建的Binder服务不会注册到ServiceManager中,这些注册到SM中的Binder服务称为实名Binder,不需要注册到ServiceManager中的Binder服务称之为匿名Binder,匿名Binder的传递和使用需要依赖于实名Binder。
借用网上某位大佬一张图,如图所示:
Android启动的时候会启动SystemServer进程,SystemServer进程的入口是里面的main方法:
public static void main(String[] args) {
new SystemServer().run();
}
这里又调用了SystemServer里面的run方法:
private void run() {
...
// Start services.
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();
startCoreServices();
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
...
}
我们看这里面的startBootstrapServices()方法:
private void startBootstrapServices() {
....
// Activity manager runs the show.
traceBeginAndSlog("StartActivityManager");
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
traceEnd();
...
traceBeginAndSlog("SetSystemProcess");
mActivityManagerService.setSystemProcess();
traceEnd();
...
}
在这里,我们看到创建了ActivityManagerService对象,并通过下面的mActivityManagerService.setSystemProcess();启动了这个ActivityManagerService。
看下ActivityManagerService中的setSystemProcess()这个方法:
public void setSystemProcess() {
...
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
...
}
在这里也就是执行了ServiceManager把创建的这个ActivityManagerService对象加入了ServiceManager中创建的映射表里。
Context.ACTIVITY_SERVICE其实就是“activity”字符串。到时候我们可以通过getService(Context.ACTIVITY_SERVICE),通过这个获取到存储在映射表里面的ActivityManagerService变量。例如:
ActivityManager activityManager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager就是一个壳,它的存在避免了AMS直接暴露给应用程序,而是通过对外提供的ActivityManager来操作AMS,这样更安全,下面我们看一下ActivityManger里面的getService方法:
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
看到这里面调用了IActivityManagerSingleton的get方法,看一下IActivityManagerSingleton:
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
首先看下Singleton类的定义:
package android.util;
/**
* Singleton helper class for lazily initialization.
*
* Modeled after frameworks/base/include/utils/Singleton.h
*
* @hide
*/
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
其中get是final的方法,不可以覆盖,create是可以覆盖的。
主要看这里的两行代码:
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
这个地方是使用了AIDL的方式(Android7.0及之前,AMS通过代理模式来完成Binder通信,8.0之后,AMS通过AIDL完成Binder通信。),通过调用ServiceManager的getService方法查询出AMS的信息并转换为代理对象am。这个am对象是IActivityManager类型的,而IActivityManager是个aidl,根据aidl的规则和原理,可以知道远程服务的具体实现一定是IActivityManager.Stub的实现类,而AMS实现了IActivityManager.Stub,所以AMS是远程服务的具体实现。
小结:上述过程
Binder IPC通信可以总结为4步:
(1)ServiceManager:首先,一个进程使用BINDER_SET_CONTEXT_MGR命令 通过BINDER驱动使自己成为ServiceManager。
(2)注册服务:Server进程首先要通过驱动注册Service到ServiceManager中,表明可以对外提供服务。该过程:Server是客户端,ServiceManager是服务端。
(3)获取服务:Client进程使用某个Service前,需先通过驱动向ServiceManager中获取相应的Service。该过程:Client是客户端,ServiceManager是服务端。
(4)使用服务:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:Client的是客户端,Server是服务端。
二、关于AIDL的使用
1、简介
AIDL(Android Interface Definition Language)Android接口定义语言,可以使用它定义客户端与服务端进程间的通信(IPC),在Android中,进程之间无法共享用户空间,不同进程之间一般通过AIDL进行通信。
AIDL中支持的数据类型如下:
- Java的8种基本数据类型:byte、short、int、long、float、double、boolean、char
- String、CharSequence、List、Map
- 自定义的数据类型
2、使用流程
(1)在项目的java文件夹同级目录创建aidl目录,然后再创建好的aidl目录下创建和java文件夹下同样的包名目录,然后在创建好的目录下创建.aidl文件
例如下图:java文件下的包名目录为:com.example.mytestapplication
那么需要在java同级目录创建名称为aidl的文件,然后在创建好的aidl目录下,创建同样的包名目录:com.example.mytestapplication。然后在创建好的目录下,创建一个.aidl文件,这里以ICompute.aidl文件为例:
interface ICompute {
int add(int a,int b);
}
在创建好的ICompute.aidl文件是一个接口,里面定义了个add方法(这里定义用于Client端与Server端通信的方法,这里的add方法是个示例)
(2)AndroidStudio执行Rebuild之后,会自动生成与.aidl文件同名的java文件
我们看下自动生成的与.aidl同名的.java文件:
public interface ICompute extends android.os.IInterface
{
/** Default implementation for ICompute. */
public static class Default implements com.example.mytestapplication.ICompute
{
@Override public int add(int a, int b) throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.mytestapplication.ICompute
{
private static final java.lang.String DESCRIPTOR = "com.example.mytestapplication.ICompute";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.mytestapplication.ICompute interface,
* generating a proxy if needed.
*/
public static com.example.mytestapplication.ICompute asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.mytestapplication.ICompute))) {
return ((com.example.mytestapplication.ICompute)iin);
}
return new com.example.mytestapplication.ICompute.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_add:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.mytestapplication.ICompute
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int add(int a, int b) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().add(a, b);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.mytestapplication.ICompute sDefaultImpl;
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.example.mytestapplication.ICompute impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.mytestapplication.ICompute getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public int add(int a, int b) throws android.os.RemoteException;
}
生成的这个文件中有一个名为Stub的子类,这个子类也是其父类接口的抽象实现,主要用于生成.aidl文件中的所有方法,Stub类声明如下:
public static abstract class Stub extends android.os.Binder implements com.example.mytestapplication.ICompute
显然,Stub实现了本地接口且集成了Binder对象,介于Binder对象在系统底层的支持下,Stub对象就具有了远程传输数据的能力,在生成Stub对象的时候会调用asInterface方法,具体如下:
public static com.example.mytestapplication.ICompute asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.mytestapplication.ICompute))) {
return ((com.example.mytestapplication.ICompute)iin);
}
return new com.example.mytestapplication.ICompute.Stub.Proxy(obj);
}
asInterface方法在Stub创建时调用,主要功能就是检索Binder对象是否是本地接口的实现,根据queryLocalInterface()方法返回值判断是否使用代理对象,这个检索过程应该由系统底层支持,如果返回null,则创建Stub的代理对象,反之使用本地对象来传输数据。
因为Stub继承了Binder类,Binder类具体如下:
// Binder
public class Binder implements IBinder {
//...
}
(3)从上面可知Stub是一个抽象类,那么它所提供的具体业务必然需要一个具体类来完成,下面实现这个具体的业务类IComputeImpl:
public class IComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
IComputeImpl类继承自ICompute.Stub,并实现了里面的方法,也就是咱们上面示例的 add方法。这里add方法的返回值,简单粗暴的直接返回a+b。
这个类就是对外提供的具体业务类,同时其示例也是一个Binder对象。
(4)创建一个Service以便对外提供具体业务,这个Service即为提供服务的一端,具体如下:
public class MyService extends Service {
public MyService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new IComputeImpl();
}
}
当客户端调用bindService()方法绑定服务时,就会调用onBind()方法返回IBinder对象,这个IBinder对象也是具体的业务对象。此外,创建的Service要在AndroidManifest.xml文件中声明,具体如下:
<service
android:name=".test.binder.server.MyService"
android:enabled="true"
android:exported="true"
android:process=":remote"/>
这里使用process关键字表示该服务开启一个独立的进程,remote可以任意,表示进程名称,将会在主进程(进程名为包名)添加新名称作为新进程的名称,如 com.example.mytestapplication 将会 变成 com.example.mytestapplication:remote
(5)客户端的调用
public class BinderTestActivity extends AppCompatActivity {
private final String TAG = BinderTestActivity.class.getSimpleName();
private ICompute iCompute;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_test);
findViewById(R.id.bt_bind_service).setOnClickListener(v -> bindService());
findViewById(R.id.bt_unbind_service).setOnClickListener(v -> unbindService());
findViewById(R.id.bt_call_remote).setOnClickListener(v -> callRemote());
}
/**
* 绑定服务
*/
public void bindService() {
Intent intent = new Intent(this, MyService.class);
//绑定服务时自动创建服务
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
/**
* 解绑服务
*/
public void unbindService() {
unbindService(conn);
}
/**
* 调用远程服务
*/
public void callRemote() {
try {
int result = iCompute.add(0, 1);
Log.e(TAG, "=======相加结果result:" + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private final ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//根据实际情况返回IBinder的本地对象或其代理对象
iCompute = ICompute.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//Service 意外中断时调用
}
};
}
这里涉及的的几步是:
- 通过bindService绑定服务
- 在ServiceConnection的onServiceConnected回调中,可以通过ICompute.Stub.asInterface(IBinder binder),得到ICompute的代理
- 然后使用ICompute的代理就可以调用服务端.aidl中定义的方法了,从而实现客户端与服务端的通信
(6)验证AIDL
上面客户端通过Binder调用服务端方法后,输出的结果:
如果想验证客户端和服务端同一进程通信的场景,创建 Service 的时候不要在 AndroidManifest.xml 文件中不要使用 process 开启独立进程即可,此时服务进程默认与客户端属于统一进程。
(7)startService()与bindService()的区别
- startService(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
- 使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止了。
(8)service的生命周期
onCreate():创建服务的时候回调
onStartCommand():开始服务的时候回调
onDestroy():销毁服务的时候回调
onBind():绑定服务时候的回调
onUnbind():解绑服务的时候回调
- 当调用Context的startService()方法的时候,会依次走onCreate()->onStartCommand(),调用stopService()方法或者stopSelf()方法的时候服务才会走onDestroy();
- 当调用Context的bindService()方法的时候,会依次走onCreate()->onBind(),调用unBindService()方法的时候才会走onUnbind()->onDestroy();
3、总结
AIDL并不等于Binder,也不是Android开发者使用Binder的唯一方式,AIDL文件只是帮我们把transact()方法的调用给隐藏起来,AIDL声明的每一个方法最终都还是会通过这个transact()方法与另一个进程的代理对象进行通信。
三、Binder有什么优势
1、性能方面
共享内存: 0 次数据拷贝
Binder:1次数据拷贝
Socket/管道/消息队列:2次数据拷贝
2、稳定性方面
Binder:基于C/S架构,客户端有什么需求就丢给服务端 去完成,架构清晰、职责明确,稳定性更好。
共享内存:虽然无需拷贝,但是控制复杂,难以使用
从稳定性的角度讲,Binder机制还是优于内存共享的。
3、安全性方面
传统的IPC没有任何安全措施,安全依赖于上层协议来确保。
传统的IPC方法无法获得对方可靠的进程用户id、进程id(UID、PID),从而无法识别对方身份。
传统的IPC只能由用户在数据包中填入UID/PID,容易被恶意程序利用。
传统的IPC访问接入点是开放的,无法阻止恶意程序通过猜测接收对方地址获得连续
Binder既支持实名Binder,又支持匿名Binder 安全性高。
总之,
- Binder(安全性高)
- 为每个APP分配不同的UID,通过UID鉴别身份
- 即支持实名Binder,又支持匿名Binder
- 传统IPC(不安全)
- 完全依赖上层协议,只能由用户在数据包中填入UID/PID。
- 访问接入点是开放的,任何程序都可以与其建立连接。
四、Binder是如何做到一次拷贝的。
Binder借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。这个映射过程是通过mmap()来实现的。
内存映射在一次Binder进程间通信:
- Binder驱动在内核空间创建一个内核接收缓存区和一个内核缓存区。
- 建立内核接收缓存区与内核缓存区的映射、内核接收缓存区与接收用户进程空间地址的映射。
- 发送方调用copy_from_user(),把数据拷贝到内核缓存区,由于它们彼此都两两存在映射关系,则接收进程也能够接收到发送进程要发送的数据。
注:一个进程分为用户空间和内核空间(Kernel)。进程间,用户空间的数据不可共享;内核空间的数据可共享。
五、为什么 Intent 不能传递大数据
Intent携带信息的大小是受Binder限制的。数据以Parcel对象的形式存放在Binder传递缓存中。如果数据或返回值buffer大,则此次传递调用失败并抛出 TransactionTooLargeException异常。Binder传递缓存有一个限定大小,通常是1MB。但同一个进程中所有的传输共享缓存空间。多个地方在进行传输时,即使它们各自传输的数据不超出大小限制,TransactionTooLargeException异常也可能会被抛出。在使用Intent传递数据时,1MB并不是安全上限。因为Binder中可能正在处理其它的传输工作。不同的机型和系统版本,这个上限值也可能会不同。
六、ServiceManager是什么
framework/native/cmds/servicemanager/
- service_manager.c
- binder.c
service_manager进程是由init进程通过解析init.rc文件来启动的。
service_manager进程是Binder IPC通信过程中的守护进程,本身也是一个Binder服务。
service_manager其功能:查询和注册服务。
ServiceManager.java是service_manager进程在java层的表现,提供查询和注册服务的功能。
ServiceManager也是一个单独的进程。要找到ServiceManager,本身也要涉及到进程间通信,从Client和Server的角度看,ServiceManager永远都是一个Server,任何访问ServiceManager 的都是Client。
为什么一个进程就能成为ServiceManager?在Android启动service_manager进程的时候,都做了什么事?
- 首先打开”/dev/binder“设备文件,映射内存
- 利用BINDER_SET_CONTEXT_MGR命令,令自己成为上下文管理者,其实也就是成为ServiceManager。
- 进入一个无限循环,等待Client的请求到来
所以通过Binder驱动,通过命令”BINDER_SET_CONTEXT_MGR“,让某个进程成为ServiceManager的。
七、为什么SystemServer进程与Zygote进程通信采用Socket而不是Binder?
首先,多线程程序里不允许使用fork(UNIX上C++程序设计守则),在“自身以外的线程存在的状态下”使用fork的话,就可能引起各种各样的问题,比较典型的例子就是,fork出来的子进程可能会死锁。
一般的,fork会做如下事情:
- 父进程的内存数据会原封不动拷贝到子进程中
- 子进程在单线程状态下被生成
如果父进程的Binder线程有锁,然后子进程的主线程一直在等待子线程(从父进程拷贝过来的子进程)的资源,但是其实父进程的子进程并没有拷贝过来,造成死锁,所以fork不允许存在多线程。而Binder通讯偏偏就是多线程操作,所以Zygote这个时候就不再选用Binder通讯,而是选用Socket通讯。
参考:
Android Framework层学习——为什么SystemServer进程与Zygote进程通讯采用Socket而不是Binder_zygote为什么用socket_Kyrie_Wangyz的博客-CSDN博客
第一节、Binder IPC - 简书