Binder机制

Binder概述

Binder在我们大Android中是无处不在的,无论是调用媒体服务,传感器,还有我们经常在用的startActivity ,startService等等都在使用着Bindder来完成相应的功能。整个Android系统就可以看成一个基于Binder的C/S架构,binder英文意思是粘合剂,Binder就是这个粘合剂,把各个组件系统粘合在一起。Binder这么重要,作为Android开发者我们也更有必要搞懂它。

下面开始学习Binder之旅吧。

Binder是用来做进程间通信的,Android系统是基于Linux的,在Linux中已经有一些进程间通信的解决方案了,比如管道,共享内存,socket等,为啥Android又弄了个Binder呢?那我们就需要了解一下他们的优缺点了

管道

就比如A到B之间有一个管道,A把数据拷贝到管道中,B从管道中读取数据,这个过程需要建立管道并需要两次数据的拷贝

而且管道是半双工的也就是数据只能往一个方向流动,如果想要双方通信,就需要建立两个管道

所以管道比较耗费性能

共享内存

多个进程之间共享一块内存区域,这个过程中无需拷贝,效率非常高,但是由于这块内存对所有进程都可见,不好管理而且安全方面也不好

Socket

Socket是一个通用的接口,主要用来进行网络之间的通信,虽然可以实现进程间通信,就是杀鸡用牛刀了,传输效率低,开销大,也需要两次的拷贝。

Binder

只需要一次数据拷贝,性能上仅次于共享内存。稳定性上Binder基于C/S架构模式,客户端有什么去求就丢给服务端去做,架构清晰职责明确。

安全方面,传统的进程间通信都没有做这一块,一个安卓系统中有那么多的APP存在,每个APP都运行在一个独立的进程中,我们不希望别的进程能够获取我们应用的信息。

Android为每个新安装的应用都分配了自己的UID,PID,这是通信时鉴别身份的重要凭证。

Binder中有4个比较重要的角色:

  • Server
  • Client
  • ServiceManager
  • Binder驱动

Binder机制_第1张图片

如上图所画

  1. 服务端通过Binder驱动在ServiceManager中注册我们的服务
  2. 客户端通过Bindr驱动查询ServiceManager中注册的服务
  3. ServiceManager通过Binder驱动返回服务端的代理对象
  4. 客户端拿到服务器端的代理对象就可以进行进程间通讯了。

Client和Server是开发者自己来实现,Binder驱动和ServiceManager是系统提供的。

Binder核心原理

  • Client端发送数据到内核缓存区也就是拷贝
  • 这个内核缓存区和Binder驱动中的数据缓存区存在映射关系
  • 服务端和Bindr的数据缓存区有直接的内存映射关系。
  • 这样就相当于把数据直接拷贝到了服务端

Binder源码(9.0)

下面的这些代码我自己也都是系统代码,我自己也云里雾里,不过我们也不需要深入了解,只需要通过这些地方来强化对其原理的理解就好了

1、打开Binder设备

源码位置:/frameworks/native/cmds/servicemanager/service_manager.c

在该文件中的main方法中有一句话 driver = "/dev/binder"; 这里就打开binder驱动设备

2、创建buffer用于进程间传递数据,开辟内存映射(128k)

第一步打开Binder驱动之后,紧接着一句代码bs = binder_open(driver, 128*1024);这里就是打开一个128k的内存映射

内存映射命令是mmap(),它在 /frameworks/native/cmds/servicemanager/binder.c文件中,进入可以看到 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

它是在系统启动的时候就会调用,在9.0系统源码/device/google/cuttlefish_kernel/4.4-x86_64/System.map文件中的25306行可以看到下面的指令
ffffffff815dbf50 t binder_mmap来开启映射

3、ServiceManager启动

在系统源码位置 /system/core/rootdir/init.rc 文件中,可以找到start servicemanager指令

4、打包到Parcel中,数据写入binder设备copy_from_user

在系统源码:/frameworks/native/libs/binder/IServiceManager.cpp中可以看到parcel打包过程

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

在系统文件 /frameworks/native/libs/binder/IPCThreadState.cpp中,找到
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);

将parcel中的信息封装成结构体并且写入到mOut中,并等待返回

5、服务注册,添加到链表svclist中

server向ServiceManager中注册

在系统代码:/frameworks/native/cmds/servicemanager/service_manager.c文件中

  if (!svc_can_register(s, len, spid, uid)) {
        ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
             str8(s, len), handle, uid);
        return -1;
    }
    //去链表链表svclist中查找,看服务是否注册过
    si = find_svc(s, len);
    ....

定义主线程中的线程池

在系统源码/frameworks/native/libs/binder/IPCThreadState.cpp文件中可以找到joinThreadPool方法。

这里就是定义一个主线程的线程池,,不停的读写

循环从mln和mOut中取出读写请求 mIn.setDataCapacity(256); mOut.setDataCapacity(256);他们默认是256字节的大小。

在talkWithDriver方法中,判断是否可以读写,最终发送到binder设备中。

这些代码真是看的云里雾里,只需要通过他们加深对Binder的执行原理就行了。

手撸AIDL

直接操作Binder是比较麻烦的,Andorid中通过AIDL来简化我们使用Binder。

AIDL四个重要对象

  • IBinder: 一个接口,代表了一个跨进程通讯的能力
  • IInterance: 服务端进程有什么能力,可以提供哪些方法
  • Binder: Binder的本地对象 继承自IBinder
  • Stub: 继承自Binder 实现了IInterance,本地实现的服务端的能力

例子:使用AIDL实现一个第三方的登录,现在有一个A应用和一个B应用,A应用调用B应用来实现登录。

最终效果如下图:

Binder机制_第2张图片

A调用B的登录服务,B是服务端,我们先从B工程中创建一个aidl,直接在工程的main文件夹上右击鼠标创建即可,也可以创建到别的文件夹。

package com.chs.binderb;

interface ILoginInterface {
    void login();

    void loginCallBack(boolean isSuccess,String user);
}

创建两个方法一个登录方法,一个登录回调。

然后把这个AIDL的完整包名和文件都复制到A工程的相同位置。必须一模一样直接复制。

在B工程中创建一个LoginService来监听A工程发来的消息,跳转到第三方登录界面,注意跳转的时候需要设置Intent.FLAG_ACTIVITY_NEW_TASK这个flag

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {
                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }

            @Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {

            }
        };
    }
}

然后在AndroidMainfest.xml文件中注册服务

 <service android:name=".service.LoginService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote_server"
            >
            <intent-filter>
                <action android:name="BinderB_Action"></action>
            </intent-filter>
</service>
  • android:enabled=“true” 表示可以被实例化
  • android:exported=“true” 表示可以被别的应用隐式调用
  • android:process=":remote_server" 表示开启一个新进程,进程名字是remote_server
  • action中的名字在 A应用隐式调用的时候使用

下面去A工程中写调用的方法

public class MainActivity extends AppCompatActivity {
    /**
     * 是否绑定了远程服务
     */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initBinderService();
    }

    /**
     * 通过隐示意图绑定B应用的service
     */
    private void initBinderService() {
        Intent intent = new Intent();
        //设置action
        intent.setAction("BinderB_Action");
        //设置B应用的包名
        intent.setPackage("com.chs.binderb");
        //绑定服务
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }

    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //绑定成功,可以使用服务端的方法了
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public void startWeiXinLogin(View view) {
        if(mILoginInterface!=null){
            try {
                mILoginInterface.login();
            } catch (RemoteException e) {
                e.printStackTrace();
                Toast.makeText(this,"请先安装微信",Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isStartRemote){
            unbindService(cnn);
        }
    }
}

布局样式就是前面gif图中的样式,微信图标的点击方法是startWeiXinLogin方法。里面调用了ILoginInterface的login方法

先说一下ILoginInterface

当我们创建好AIDL文件,重新Rebuild一下工程之后,系统会给我们生成一个ILoginInterface文件,位置在 app\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\chs\binderb\ILoginInterface.java

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\android\\A1\\BinderA\\app\\src\\main\\aidl\\com\\chs\\binderb\\ILoginInterface.aidl
 */
package com.chs.binderb;

public interface ILoginInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.chs.binderb.ILoginInterface {
        private static final java.lang.String DESCRIPTOR = "com.chs.binderb.ILoginInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.chs.binderb.ILoginInterface interface,
         * generating a proxy if needed.
         */
        public static com.chs.binderb.ILoginInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.chs.binderb.ILoginInterface))) {
                return ((com.chs.binderb.ILoginInterface) iin);
            }
            return new com.chs.binderb.ILoginInterface.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_login: {
                    data.enforceInterface(descriptor);
                    this.login();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_loginCallBack: {
                    data.enforceInterface(descriptor);
                    boolean _arg0;
                    _arg0 = (0 != data.readInt());
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    this.loginCallBack(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.chs.binderb.ILoginInterface {
            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 void login() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(((isSuccess) ? (1) : (0)));
                    _data.writeString(user);
                    mRemote.transact(Stub.TRANSACTION_loginCallBack, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_loginCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void login() throws android.os.RemoteException;

    public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException;
}

它继承了IInterface接口,所有可以在Binder中传输的接口都需要继承IInterface接口。同时它自己也是一个接口。

它声明了两个方法login()和loginCallBack就是我们在AIDL文件中写的两个方法。同时声明了两个整形id:TRANSACTION_login和TRANSACTION_loginCallBack来标识这两个方法。在onTransact方法中通过这两个id来识别客户端请求的是哪个方法

它内部有一个内部类Stub,这个就是一个Binder,跨进程通信的过程就由它的内部代理Proxy完成,它里面有几个重要的方法

asInterface

用于将服务端的Binder对象转化成客户端可以使用的AIDL接口类型的对象。这个转化是分进程的,如果客户端和服务端在同一个进程中就返回Stub本身,如果是在不同的进程中,就返回Stub.Proxy(obj)代理对象

asBinder

返回当前的Binder对象

onTransact

这个方法时重写的Binder类中的onTransact方法。它运行在服务端的Binder线程池中,远程客户端发起请求时,请求会经过系统包装后交给该方法来处理。它通过不同的code来判断调用哪个方法。然后执行方法并写入返回值

Proxy#login

这个方法运行在客户端,前面的MainActivity中我们调用asInterface方法其实就是拿到了这个Proxy对象,所以我们就能调用它的login方法,当客户端调用该方法的时候创建输入的Parcel对象_data和输出的Parcel对象 _reply,然后调用transact方法来发起远程调用请求,然后当前线程挂起,之后服务端的onTransact方法会被调用,直到完成并返回结果

Proxy#loginCallBack

和上面的login方法一样。

OK 现在回到MainActivity中,在onCreate方法中通过隐式的调用绑定B应用中的服务。

这样点击按钮的时候,B应用中的LoginService的onBind方法就会调用,然后就会打开登录的Activity。

到这里其实A到B的跨进程通信就已经完成了,但是我们在B应用中点击输入用户名和密码如果成功或者失败,应该反馈给A应用啊,怎么反馈呢。

方法就是跟A找B通信时一样的道理,在A中也写一个Service,让B去绑定A中的Service,执行完登录之后,调用A的远程方法。

代码如下

public class LoginService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginInterface.Stub() {
            @Override
            public void login() throws RemoteException {

            }

            @Override
            public void loginCallBack(boolean isSuccess, String user) throws RemoteException {
                Log.i("登录情况","状态:"+isSuccess+">>>>>user:"+user);
            }
        };
    }
}

A中也写一个LoginService,在回调方法中打印一下回调状态和用户名,并在AndroidMasfet.xml中注册

B中模拟登录并调用A中服务的方法

public class MainActivity extends AppCompatActivity {
    private static final String NAME = "chs";
    private static final String PWD = "123";


    private EditText etName;
    private EditText etPwd;
    /**
     * 是否绑定了远程服务
     */
    private boolean isStartRemote;
    private ILoginInterface mILoginInterface;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etName = findViewById(R.id.et_name);
        etPwd = findViewById(R.id.et_pwd);

        initBinderService();

    }

    /**
     * 通过隐示意图绑定A应用的service
     */
    private void initBinderService() {
        Intent intent = new Intent();
        //设置action
        intent.setAction("BinderA_Action");
        //设置B应用的包名
        intent.setPackage("com.chs.bindera");
        //绑定服务
        bindService(intent,cnn,BIND_AUTO_CREATE);
        isStartRemote = true;
    }
    ServiceConnection cnn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //绑定成功,可以使用服务端的方法了
            mILoginInterface = ILoginInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    public void qqLogin(View view) {
        final String name = etName.getText().toString();
        final String pwd = etPwd.getText().toString();
        //ProgressDialog dialog = new ProgressDialog(this);
        //dialog.setTitle("正在登录");
        new Thread(){
            @Override
            public void run() {
                super.run();
                SystemClock.sleep(1000);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        boolean isSuccess = false;
                        if(name.equals(NAME)&&pwd.equals(PWD)){
                            isSuccess = true;
                            showToast("登录成功");
                            finish();
                        }else {
                            showToast("登录失败");
                        }
                        try {
                            mILoginInterface.loginCallBack(isSuccess,name);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }.start();
    }

    private void showToast(String text) {
        Toast.makeText(this,text,Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isStartRemote){
            unbindService(cnn);
        }
    }
}

OK代码完成,运行之后就是前面gif中的效果了。A中LoginService中的回调打印如下。

2019-07-10 21:51:27.225 10173-10191/com.chs.bindera:remote_a I/登录情况: 状态:false>>>>>user:
2019-07-10 21:51:35.343 10173-10191/com.chs.bindera:remote_a I/登录情况: 状态:true>>>>>user:chs

你可能感兴趣的:(Framework)