Android中关于Binder机制的一些感悟

前言

最近重温了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 - 简书

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值