安卓移动架构07-Binder核心机制

安卓移动架构07-Binder核心机制

什么是Binder

  1. 从机制上说,Binder是一种Android中实现跨进程通信(IPC)的方式;
  2. 从设备上说,Binder是一种虚拟的物理设备驱动;
  3. 从代码上说,BInder 是IBinder接口的实现类,将Binder机制模型以代码的形式 实现在整个Android系统中。

为什么要使用Binder

Android系统底层是Linux系统,Linux系统原本就具有IPC方式,但是Andriod为什么不使用Linux的IPC方式,而要设计Binder机制呢?

Linux的IPC方式主要有8种:

  1. 管道 (PIPE),实际是用于进程间通信的一段共享内存。缺点:需要将一块内存拷贝两次,第一次拷贝到共享区,第二次拷贝到目标进程,效率低。
  2. 命名管道(FIFO),是一种特殊类型的文件。缺点:是基于文件的io操作,性能差。
  3. 信号 (signal),是一种以事件为驱动的通信方式。缺点:不适合信息的传递。
  4. 消息队列(Message queues),是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容。缺点:跟管道一样。
  5. 信号量(Semaphore),是一种计数器,用于控制对多个进程共享的资源进行的访问。缺点:跟信号一样。
  6. 共享内存(Share Memory),是在多个进程之间共享内存区域的一种进程间的通信方式。缺点:需要自己实现进程间的同步。
  7. 内存映射(Memory Map),是由一个文件到一块内存的映射。缺点:需要自己实现进程间的同步。
  8. 套接字 (Socket),是一种跨网络的通信方式。缺点:传输效率低,开销大。

Binder相比传统的IPC方式,主要有两个优点:

  1. 效率高。Binder采用内存共享的机制,但是只要进行一次内存拷贝。
  2. 更安全。Linux的IPC方式无法获取目标进程的UID/PID,无法鉴别身份;Android可以获取目标进程的进程的UID/PID,从而控制访问权限。

Binder的使用

Binder依赖于Service,在组件(Activty)中通过bindService(),就可以获取Service中的Binder对象,实现Service与Activity的通信。

服务分为本地服务和远程服务,都可以使用Binder。

1 本地服务

在本地服务中使用Binder,只需要两步:

  1. 声明一个Service,重写onBind(),返回一个继承Binder的自定义对象;
  2. 声明一个ServiceConnection,重写onServiceConnected(),获取IBinder对象,然后调用Activity的bindService();

声明Service:

public class Service2 extends Service {
    String TAG = "TestService";
    MyBinder myBinder = new MyBinder();

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind() executed");
        return myBinder;
    }

    //定义一个类实现IBinder接口(Binder实现了IBinder接口)
    class MyBinder extends Binder {
        public void doSomething() {
            Log.d("TAG", "doSomething() executed");
        }
    }
}

在Activity中绑定服务:

private void myBindService() {
    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (Service2.MyBinder) service;
            myBinder.doSomething();
        }
        
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected() executed");
        }
    };
    Intent bindIntent = new Intent(this, Service2.class);
    //此时使用bindService开启服务
    bindService(bindIntent, connection, BIND_AUTO_CREATE);
    //销毁服务
    unbindService(connection);
}

2 远程服务

在远程服务中使用Binder,需要三步:创建aidl、声明Service、调用bindService。

2.1 创建aidl

aidl是进程间通信接口,需要在客户端(声明Service的应用)和服务端(调用Service的应用)同时声明,并且要完全一致。

首先,在服务端端创建aidl:选中项目目录->右键选中new->然后选中AIDL->填写文件名(比如:TestAidl)->修改aidl的代码。

// TestAidl.aidl
package gsw.demopluggable;

// Declare any non-default types here with import statements
interface TestAidl {
    String getName();

    String setName(String name);
}

然后,在客户端创建aidl,步骤同上。客户端的aidl要与服务端的一模一样,包括包名、类名、方法。

然后,选中Build->Make Project,编译整个工程,就会在build/generated/source/aidl/debug目录下生产一个名为TestAidl的接口。

2.2 声明Service

在服务端,声明一个类(比如:MyStub)继承TestAidl.Stub(TestAidl的代理类,系统帮我们生成的),然后声明一个继承Service的类,在onBind()中返回MyStub对象。

public class ServiceBinder extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyStub();
    }

    public static class MyStub extends TestAidl.Stub {
        String name = null;

        @Override
        public String getName() throws RemoteException {
            return name;
        }

        @Override
        public String setName(String name) throws RemoteException {
            this.name = name;
            return name;
        }
    }
}

2.3 调用bindService

在客户端,声明一个匿名内部类ServiceConnection,重写onServiceConnected(),通过TestAidl.Stub.asInterface(iBinder)获取服务端的TestAidl对象。

然后,通过远程服务的action调用bindService()。

/**
 * 绑定远程服务
 * action为"gsw.demopluggable2.binder.ServiceBinder"
 */
private void bindRemoteServer() {
    Intent intent = new Intent();
    intent.setAction("gsw.demopluggable2.binder.ServiceBinder");
    intent.setPackage("gsw.demopluggable2");

    bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            TestAidl testAidl = TestAidl.Stub.asInterface(iBinder);

            try {
                testAidl.setName("David");
                Toast.makeText(ActivityBinder.this, "--->  " + testAidl.getName(), Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    }, Context.BIND_AUTO_CREATE);
}

3 使用自定义类型

aidl中,使用自定义类型,需要在创建一个对应的aidl文件来声明这个类型,并且这个类必须为Parcelable类型。

首先,创建Java类,实现Parcelable接口。

package gsw.demopluggable.binder;
import android.os.Parcel;
import android.os.Parcelable;

public class Item1 implements Parcelable {
    public String name;
    public int id;
    protected Item1(Parcel in) {
        name = in.readString();
        id = in.readInt();
    }
    public Item1(String name, int id) {
        this.name = name;
        this.id = id;
    }
    public static final Creator CREATOR = new Creator() {
        @Override
        public Item1 createFromParcel(Parcel in) {
            return new Item1(in);
        }
        @Override
        public Item1[] newArray(int size) {
            return new Item1[size];
        }
    };
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(id);
    }
}

然后,创建一个对应的aidl文件:Item1.adil。它们两个的包名必须相同,且必须服务端的包名相同。

// Item1.aidl
package gsw.demopluggable.binder;
//声明是Parcelable类型,作为参数使用
parcelable Item1;

然后,就可以在其它的aidl中引用Item1.adil。

// TestAidl.aidl
package gsw.demopluggable;
//import自定义类型
import gsw.demopluggable.binder.Item1;
import gsw.demopluggable.binder.Item2;

// Declare any non-default types here with import statements
interface TestAidl {
    String getName();

    String setName(String name);

    //Item1作为参数时,需要在前面加in,代表输入类型
    Item2 getItem2(in Item1 item1);
}

Binder的原理

1 内核共享

Binder是通过共享内存的方式,实现进程间通信的。

那么,共享的是哪一块内存呢?我们看下面这张图:

[图片上传失败...(image-b9913e-1564362909580)]

每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。

Client进程向Server进程通信,就是获取Server进程的Binder对象,只需要从Server进程的用户空间拷贝一份内存到Client进程的用户空间。

2 通信流程

通信流程可以分为3步:注册服务、获取服务、使用服务。

[图片上传失败...(image-c63380-1564362909581)]

首先需要注册服务,服务端通过 ServiceManager 注册服务。注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。

然后是获取服务端。获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。

最后是使用服务。使用服务就是通过Binder驱动将服务端的Binder对象,拷贝到客户端,然后通过Binder对象进行通讯。

注意:客户端、服务端、ServiceManager都在不同的进程,它们之间都是通过Binder机制进行通讯的。

3 aidl机制

上面的通信流程好像跟aidl没什么关系,但是实际使用时却不能少,这是为什么呢?

aidl是进程间通信接口,它不是Java的类,更像一种协议。它的作用是让AS自动生成Binder类,供客户端调用。

aidl文件编译后,会生成一个Java接口,分为3层:TestAidl、Stub、Proxy。

package gsw.demopluggable;

public interface TestAidl extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements gsw.demopluggable.TestAidl {
        ...
    }
    private static class Proxy implements gsw.demopluggable.TestAidl {
       ...     
    }
    public java.lang.String getName() throws android.os.RemoteException;

    public java.lang.String setName(java.lang.String name) throws android.os.RemoteException;
}

3.1 TestAidl

TestAidl就是aidl文件中声明的接口,也就是我们要给客户端调用的功能。

3.2 Proxy

Proxy,顾名思义,就是TestAidl的代理类,用来实现TestAidl的方法。Proxy的作用是将需要传递的参数转化为Parcel,从而跨进程传输。

看下面的例子:

public java.lang.String getName() throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
        //用来标识_data是给含有DESCRIPTOR标志的Binder接口的参数
        _data.writeInterfaceToken(DESCRIPTOR);
        //调用Stub的transact,处理getName请求
        mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
        _reply.readException();
        //获取Stub的处理结果
        _result = _reply.readString();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

3.3 Stub

Stub是一个使用TestAidl装饰的Binder类,是我们实际传递给客户端的对象。通过Stub,客户端就可以调用TestAidl的方法。

首先是DESCRIPTOR,默认值是包名+类名,作用是在IBinder中查找TestAidl接口。

private static final java.lang.String DESCRIPTOR = "gsw.demopluggable.TestAidl";

然后是asInterface(),作用是返回Proxy对象,也就是把TsetAidl对象返回给客户端。

public static gsw.demopluggable.TestAidl asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof gsw.demopluggable.TestAidl))) {
        return ((gsw.demopluggable.TestAidl) iin);
    }
    return new gsw.demopluggable.TestAidl.Stub.Proxy(obj);
}

然后是onTransact(),作用是处理客户端的请求,并将处理结果返回给客户端。

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        //用来指定DESCRIPTOR,从而确定需要通讯的接口
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        //用来处理TestAidl的getName()
        case TRANSACTION_getName: {
            //说明data正准备给指定DESCRIPTOR的接口使用
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _result = this.getName();
            //说明当前操作没有出现异常
            reply.writeNoException();
            reply.writeString(_result);
            return true;
        }
        //用来处理TestAidl的setName()
        case TRANSACTION_setName: {
            //说明data正准备给指定DESCRIPTOR的接口使用
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _arg0;
            _arg0 = data.readString();
            java.lang.String _result = this.setName(_arg0);
            //说明当前操作没有出现异常
            reply.writeNoException();
            reply.writeString(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

最后

代码:https://gitee.com/yanhuo2008/AndroidCommon/tree/master/DemoPluggable

参考文章:

https://blog.csdn.net/a987073381/article/details/52006729

https://blog.csdn.net/freekiteyu/article/details/70082302

你可能感兴趣的:(安卓移动架构07-Binder核心机制)