43

Binder机制

 4 years ago
source link: https://www.tuicool.com/articles/V73uYrv
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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驱动

FbEfmu2.png!web

如上图所画

  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<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中,并等待返回

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应用来实现登录。

最终效果如下图:

NviiUz2.gif

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK