Binder 应用概述

2016-04-07  本文已影响0人  dragonZ龙
本文目的

理解Binder对于理解整个Android系统有着非常重要的作用,Android系统的四大组件,AMS,PMS等系统服务无一不与Binder挂钩;如果对Binder不甚了解,那么就很难了解这些系统机制.

要真正的弄明白Binder机制还是比较麻烦的,本文只是大致的介绍一下相关的概念以及在应用层该怎么使用.

本文目标:

  1. 不依赖AIDL工具,手写远程Service完成跨进程通信
Linux相关概念

因为是讲进程间的通信,而android又是基于linux,所以对于linux系统需要一定的了解.
推荐 linux内核设计与实现 一书,其主要是讲一些系统概念.

什么是Binder驱动

由于Linux的动态加载内核模块的机制,这样Android系统就可以在Linux的基础之上,通过添加一个内核模块运行在内核空间,用户进程之间可通过这个模块完成通信。这个模块就是所谓的 Binder驱动 .

尽管名叫驱动,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的:它工作于内核态,提供open(),mmap(),poll(),ioctl()等标准文件设备的操作.

为何使用Binder

为什么要单独弄一套, 而不是使用linux系统提供的那些进程间通信的方式

主要是考虑到性能和安全,还有易用性.

Binder通信模型

应用层大家所熟知的通信结构, 如下图:


binder通信结构.jpg
  1. 从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;

  2. 实际上,代理接口中定义的方法与server中定义的方法是一一对应的;

  3. client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;

  4. 代理接口将该Parcel发送给内核中的binder driver.

  5. server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;

  6. 整个的调用过程是一个同步过程,在server处理的时候,client会block住。

在整个Binder系统中,Binder框架定义了四个角色:Server,Client,ServiceManager 以及Binder驱动。其中Server,Client,SM运行于用户空间,驱动运行于内核空间,他们之间的关系如下图(参考老罗的Android之旅-Binder篇):

Binder各角色之间的关系.jpg

说明如下:

  1. Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中

  2. Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server

  3. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信

  4. Client和Server之间的进程间通信通过Binder驱动程序间接实现

  5. Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力

可以看出驱动是整个通信过程的核心,完成跨进程通信的秘密全部隐藏在驱动里面;这里Client与SM的通信,以及Client与Server的通信,都会经过驱动

相关接口(addService/getService)可参见 native/libs/binder/IServiceManager.cpp 以及对应的 service_manager.c 文件

Binder机制跨进程原理
binder-跨进程原理.jpg
  1. 首先Server进程要向SM注册;告诉自己是谁,自己有什么能力;在这个场景就是Server告诉SM,它叫Server_A,它有一个object对象,可以执行add 操作;于是SM建立了一张表:Server_A这个名字对应进程Server; 如原代码中 .//native/libs/binder/IServiceManager.cpp, 它会将server名以及对应的server进程通过Parcel给到Binder Driver中去.

     virtual status_t addService(const String16& name, const sp<IBinder>& service,
         bool allowIsolated)
     {
         Parcel data, reply;
     data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
         data.writeString16(name);
         data.writeStrongBinder(service);
         data.writeInt32(allowIsolated ? 1 : 0);
         status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
         return err == NO_ERROR ? reply.readExceptionCode() : err;
     }
    

Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程是在Binder跨进程穿越的时候,它在一个进程留下了一个本体,在另外一个进程则使用该对象的一个proxy;Client进程的操作其实是对于proxy的操作,proxy利用Binder驱动最终让真正的binder对象完成操作。

Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程,那么直接返回原始的Binder实体;如果在不同进程,那么就给它一个代理对象- 如后面demo中的代码...

public static ICalculate asInterface(IBinder obj) {
        if(obj == null) {
            return null;
        } else {
            IInterface iin = obj.queryLocalInterface("com.zhangfl.jpush.ICalculate");
            
            return (ICalculate)(iin != null && iin instanceof ICalculate?(ICalculate)iin:new ICalculate.Stub.Proxy(obj));
        }
    }

Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

proxy代理模式

模式中的三种角色:

proxy.jpg

模式原则: 对修改关闭,对扩展开放,保证了系统的稳定性

驱动里面的Binder

略过: 具体可以参考binder.c源码以及 Binder设计与实现 一文

Java层的Binder

IBinder/IInterface/Binder/BinderProxy/Stub

Demo - 不依赖AIDL工具,手写远程Service完成跨进程通信

通过上面的一些概念以及Binder相关的设计论述,我们手写远程Service完成跨进程通信就很简单了.

client:

private IBinder mRemote = null;
private ServiceConnection serviceConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
        mRemote = service;
    }
   
    @Override
    public void onServiceDisconnected(ComponentName name) {}
};

private void binder() {
    Intent intent = new Intent(BinderActivity.this, CalculateService.class);
    bindService(intent,serviceConn, Context.BIND_AUTO_CREATE);
}
    
private void add() {
    String strA = etFirst.getText().toString();
    String strB = etSecond.getText().toString();
    int a = (StringUtils.isEmpty(strA) ? 0 : Integer.parseInt(strA));
    int b = ((StringUtils.isEmpty(strB) ? 0 : Integer.parseInt(strB)));

    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();
    int _result = -100;

    try {
        _data.writeInt(a);
        _data.writeInt(b);
        mRemote.transact(1, _data, _reply, 0);
        _result = _reply.readInt();
    } catch (RemoteException e) {
        Logger.e(TAG, "RemoteException:", e);
    } finally {
        _reply.recycle();
        _data.recycle();
    }

    Logger.d(TAG, "binder:" + a + " + " + b + " = " + _result);
}

Server:

public class CalculateService extends Service {

    private static final String TAG = "CalculateService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Logger.d(TAG, "binder success");
        return binder;
    }

    private IBinder binder = new Binder() {
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            if (code == 1) {
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.add(_arg0, _arg1);
                reply.writeInt(_result);
                return true;
            }
            return super.onTransact(code, data, reply, flags);
        }

        public int add(int a, int b) {
            Logger.d(TAG, "PID:" + android.os.Process.myPid());
            Logger.d(TAG, "a:" + a + ", b:" + b);
            return a + b;
        }
    };
}
  1. 首先client通过 binder()得到 server端的 IBinder(我们已经知道,IBinder是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递)

  2. 然后客户端调用 mRemote.transact方法完成进程间的通信: mRemote是远程对象,在调用transact方法会执行onTransact方法;通过把Client端的参数转换成Parcel(_data)传递到Server端

  3. 最后在server端执行onTransact方法,解包Parcel对象,得到由client传入的参数,最终将执行结果封包成Parcel对象给到client, client最后也通过解包得到相应的结果.这样整个过程就形成了一次跨进程之间的通信.

PS: 记得在manifest.xml中将 server配成远程服务.

demo - 通过aidl来实现跨进程通信

client:

private ICalculate calculate = null;
private ServiceConnection serviceConnectionAidl = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        calculate = ICalculate.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

private void aidlBinder() {
    Intent intent = new Intent(BinderActivity.this, CalculateAidlService.class);
    bindService(intent,serviceConnectionAidl, Context.BIND_AUTO_CREATE);
}

private void addByAidl() {
    String strA = etFirst.getText().toString();
    String strB = etSecond.getText().toString();
    int a = (StringUtils.isEmpty(strA) ? 0 : Integer.parseInt(strA));
    int b = ((StringUtils.isEmpty(strB) ? 0 : Integer.parseInt(strB)));
    
    try {
        int result = calculate.add(a, b);
        Logger.d(TAG, "aidl:" + a + " + " + b + " = " + result);
    } catch (RemoteException e) {
        Logger.e(TAG, "RemoteException:", e);
    }
}

server:

public class CalculateAidlService extends Service {

    private static final String TAG = "CalculateAidlService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Logger.d(TAG, "binder success");
        return binder;
    }

    private IBinder binder = new ICalculate.Stub() {

        @Override
        public int add(int a, int b) throws RemoteException {
            Logger.d(TAG, "PID:" + android.os.Process.myPid());
            Logger.d(TAG, "a:" + a + ", b:" + b);
            return a + b;
        }
    };
}

通过之前的分析以及同自己写远程server的对比,我们可以看出通过aidl方式来实现跨进程通信是多么的简洁. 可以看看aidl自动生成的代码在背后做了些什么.

aidl interface:

// ICalculate.aidl
package com.zhangfl.jpush;

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

interface ICalculate {
    int add(int a, int b);
}

上面的interface通过aidl工具生成的相关代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zhangfl.jpush;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

public interface ICalculate extends IInterface {
    int add(int var1, int var2) throws RemoteException;

    public abstract static class Stub extends Binder implements ICalculate {
        private static final String DESCRIPTOR = "com.zhangfl.jpush.ICalculate";
        static final int TRANSACTION_add = 1;

        public Stub() {
            this.attachInterface(this, "com.zhangfl.jpush.ICalculate");
        }

        public static ICalculate asInterface(IBinder obj) {
            if(obj == null) {
                return null;
            } else {
                IInterface iin = obj.queryLocalInterface("com.zhangfl.jpush.ICalculate");
                return (ICalculate)(iin != null && iin instanceof ICalculate?(ICalculate)iin:new ICalculate.Stub.Proxy(obj));
            }
        }

        public IBinder asBinder() {
            return this;
        }

        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch(code) {
                case 1:
                    data.enforceInterface("com.zhangfl.jpush.ICalculate");
                    int _arg0 = data.readInt();
                    int _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                case 1598968902:
                    reply.writeString("com.zhangfl.jpush.ICalculate");
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
            }
        }

        private static class Proxy implements ICalculate {
            private IBinder mRemote;

            Proxy(IBinder remote) {
                this.mRemote = remote;
            }

            public IBinder asBinder() {
                return this.mRemote;
            }

            public String getInterfaceDescriptor() {
                return "com.zhangfl.jpush.ICalculate";
            }

            public int add(int a, int b) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();

                int _result;
                try {
                    _data.writeInterfaceToken("com.zhangfl.jpush.ICalculate");
                    _data.writeInt(a);
                    _data.writeInt(b);
                    this.mRemote.transact(1, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }
        }
    }
}

在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要自己手动完成(其实也仅仅只是完成具体的功能而已,完全没必要理会跨进程;因为aidl生成的代码在底层已经完全实现了,大家仔细看看就非常清楚它同我们自己手写的远程server的代码非常的像,只是用了一下proxy的设计模式,包装了一下而已).

Proxy与Stub不一样,虽然他们都既是Binder又是IInterface,不同的是Stub采用的是继承(is 关系), Proxy采用的是组合(has 关系)。他们均实现了所有的IInterface函数,不同的是,Stub使用策略模式 调用的是虚函数(待子类实现),而Proxy则使用组合模式。为什么Stub采用继承而Proxy采用组合? 事实上,Stub本身is一个IBinder(Binder),它本身就是一个能跨越进程边界传输的对象,所以它得继 承IBinder实现transact这个函数从而得到跨越进程的能力(这个能力由驱动赋予)。Proxy类使用组合,是因为他不关心自己是什么,它也不需要跨越进程传输,它只需要拥有这个能力即可,要拥有这个能力,只需要保留一个对IBinder的引用.

AIDL过程分析, 一种固定的模式:

系统服务分析

IXXX、IXXX.Stub和IXXX.Stub.Proxy,并做好对应。这样看相关的系统服务就比较容易了,以ServiceManager为例

实际上ServerManager既是系统服务的管理者,同时也是一个系统服务。因此它肯定是基于Binder实现的

再看看ActivityManager中的Binder。

参考

上一篇 下一篇

猜你喜欢

热点阅读