Binder机制

Binder概述

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

下面开始学习Binder之旅吧。android

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

管道缓存

就好比A到B之间有一个管道,A把数据拷贝到管道中,B从管道中读取数据,这个过程须要创建管道并须要两次数据的拷贝安全

并且管道是半双工的也就是数据只能往一个方向流动,若是想要双方通讯,就须要创建两个管道bash

因此管道比较耗费性能服务器

共享内存微信

多个进程之间共享一块内存区域,这个过程当中无需拷贝,效率很是高,可是因为这块内存对全部进程均可见,很差管理并且安全方面也很差网络

Socket架构

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

Binder

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

安全方面,传统的进程间通讯都没有作这一块,一个安卓系统中有那么多的APP存在,每一个APP都运行在一个独立的进程中,咱们不但愿别的进程可以获取咱们应用的信息。

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

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

  • Server
  • Client
  • ServiceManager
  • Binder驱动

如上图所画

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

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

Binder核心原理

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

Binder源码(9.0)

下面的这些代码我本身也都是系统代码,我本身也云里雾里,不过咱们也不须要深刻了解,只须要经过这些地方来强化对其原理的理解就行了

一、打开Binder设备

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

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

二、建立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来开启映射

三、ServiceManager启动

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

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

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

virtual status_t addService(const String16& name, const sp<IBinder>& 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中,并等待返回

五、服务注册,添加到链表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应用来实现登陆。

最终效果以下图:

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
复制代码
相关文章
相关标签/搜索