我也想聊聊Binder机制

栏目: Android · 发布时间: 6年前

内容简介:想写篇关于Binder的文章,可对其一无所知,无从下手。在阅读了大量的优秀文章后,心惊胆战的提笔,不怕文章被贻笑大方,怕的是误人子弟!望各位大佬抽空阅读本文的同时,能够对文章的知识点持怀疑态度,共同探讨,共同进步!日常开发中,通过Intent携带数据跳转Activity时,数据通常要通过实现Serializable或Parcelable接口,才能在被Intent所携带,而Serializable接口和Parcelabel接口主要是完成对象的序列化过程。将对象持久化到设备上或者网络传输同样也需要序列化。Ser

想写篇关于Binder的文章,可对其一无所知,无从下手。在阅读了大量的优秀文章后,心惊胆战的提笔,不怕文章被贻笑大方,怕的是误人子弟!望各位大佬抽空阅读本文的同时,能够对文章的知识点持怀疑态度,共同探讨,共同进步!

我也想聊聊Binder机制

一、序列化

日常开发中,通过Intent携带数据跳转Activity时,数据通常要通过实现Serializable或Parcelable接口,才能在被Intent所携带,而Serializable接口和Parcelabel接口主要是完成对象的序列化过程。将对象持久化到设备上或者网络传输同样也需要序列化。

1.Serializable 接口

Serializable接口是 Java 所提供的,为对象提供标准的序列化和反序列化操作。通常一个对象实现Serializable接口,该对象就具有被序列化和反序列化的能力,而且几乎所有工作有系统自动完成。Serializable接口内serialVersionID可指定也可以不指定,其作用是用来判断序列化前和反序列化的类版本是否发生变化。该变量如果值不一致,表示类中某些属性或者方法发生了更改,反序列化则出问题。(静态成员变量和transient关键字标记的成员不参与序列化过程)

2.Parcelable 接口

Parcelable 接口是Android所提供的,其实现相对来说比价复杂。实现该接口的类的对象就可以在Intent和Binder进行传递。

3.两者的区别

Serializable是Java提供的接口,使用简单,但序列化与反序列化需要大量的IO操作,所以开销比较大。Parcelable是Android提供的序列化方法,使用麻烦当效率高。在Android开发中,将对象序列化到设备或者序列化后通过网络传输建议使用Serializable接口,其他情况建议是用Parcelable接口,尤其在内存的序列化上。例如Intent和Binder传输数据。

二、AIDL

在Java层,想利用Binder进行夸进程的通信,那就得通过AIDL(Android 接口定义语言)了,AIDL是客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口,只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL,如果是在单应用(单进程),建议使用Messager。

1、AIDL支持的数据类型

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
  • String 和 CharSequence
  • 所有实现了Parcelable接口的对象
  • AIDL接口
  • List,目前List只支持ArrayList类型,持有元素必须是以上讲到类型。
  • Map,目前只支持HashMap类型,持有元素必须是以上讲到类型。

自定义的Parcelable对象和AIDL接口必须显示导入到AIDL文件中。

数据的走向

Parcelable对象和AIDL接口在使用前必须标明数据的走向:

  • in 客户端流向服务端
  • out 服务端流向客户端
  • inout 服务端与客户端可进行交互

示例:

void addUser(inout User user);
复制代码

2、服务端的实现

2.1、定义数据对象

定义一个实现了Parcelable 接口,作为客户端和服务端传输的数据对象。

public class User implements Parcelable {

    private String username;

    private String address;

    public User() {
    }
    
    public User(String username, String address) {
        this.username = username;
        this.address = address;
    }

    User(Parcel in) {
       readFromParcel(in);
    }
    //系统默认生成,反序列化过程,我们只需要要构造方法读取相关值就可以
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
     //系统默认生成,内容描述功能,几乎所有情况下都返回0,
     //仅仅当前存在文件描述符,才返回1
    @Override
    public int describeContents() {
        return 0;
    }
    //序列化过程,通过一系列的write将值写到Parcel 对象
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(username);
        dest.writeString(address);
    }
    
    @Override
    public String toString() {
        return username+":"+address;
    }

    public void readFromParcel(Parcel in){
        username=in.readString();
        address=in.readString();
    }
}
复制代码

2.2、抽象服务端服务

通过下面方法,建立一个UserManger.aidl文件,表示服务端能为客户端提供什么样的服务。

我也想聊聊Binder机制
下面代码通过建立UserManager.aidl文件,为客户端提供 addUsergetUser

的能力。UserManager可以理解为,服务端和客户端的共同约定,两者能进行怎么样的交互。

package com.gitcode.server;

// 在这里要导入传递对象的类型,例如User
import com.gitcode.server.User;

interface UserManager {

    void addUser(inout User user);

    User getUser(int index);
}
复制代码

定义UserManager.aidl文件后,系统默认会生成UserManager.java文件。

我也想聊聊Binder机制

UserManager.java的代码如下,为了减少篇幅,去掉了一些实现。

public interface UserManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.gitcode.server.UserManager {
        private static final String DESCRIPTOR = "com.gitcode.server.UserManager";
        
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        
        public static com.gitcode.server.UserManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.gitcode.server.UserManager))) {
                return ((com.gitcode.server.UserManager) iin);
            }
            return new 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 {
           ......
        }

        private static class Proxy implements com.gitcode.server.UserManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void addUser(com.gitcode.server.User user) throws android.os.RemoteException {
                 ......
            }

            @Override
            public com.gitcode.server.User getUser(int index) throws android.os.RemoteException {
                .....
            }
        }

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

    public void addUser(com.gitcode.server.User user) throws android.os.RemoteException;

    public com.gitcode.server.User getUser(int index) throws android.os.RemoteException;
}

复制代码

从上文可知,UserManager本身是一个接口,并继承IInterface接口。UserManager.java声明了 addUsergetUser ,和在UserManager.aidl的声明是一致的。同时声明两个整型 TRANSACTION_addUserTRANSACTION_getUser ,用于在 transact() 方法中标识调用服务端哪个方法。如果服务端和客户端在不同进程,方法调用会走 transact() 方法,逻辑由Stub 和Proxy 内部类完成。

内部类 Stub 的一些概念和方法含义:

DESCRIPTOR

Binder的唯一标识,一般用当前的类名全名标识。

asInterface(IBinder obj)

将服务端的Binder对象转换成客户端的AIDL接口类型的对象,如果客户端和服务端同一进程,直接返回Stub对象本身,不在同一进程,则返回由系统封装的Stub.proxy对象。

asBinder

返回当前Binder对象

onTransact(int code, Parcel data, Parcel reply, int flags)

运行在服务端Binder线程池,当客户端跨进程发起请求后,系统封装后交由此方法来处理。code表示调用服务端什么方法,上文声明的整型。data表示客户端传递过来的数据,reply为服务端对客户端的回复。

内部代理类 Poxy ,表示客户端远程能对服务端进行的操作。

addUser运行在客户端,当客户端远程调用时,

在相同目录下创建User.aidl,可以直接复制UserManager.aidl,内容修改如下。

package com.gitcode.server;

parcelable User;
复制代码

在服务端中,服务一般以Service体现,定义UserServcie,继承Service。

public class UserService extends Service {
    private static final String TAG = "Server";
    private List<User> list = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        list.add(new User("GitCode", "深圳"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"on Bind");
        return stub;
    }


    private UserManager.Stub stub = new UserManager.Stub() {
        @Override
        public void addUser(User user) throws RemoteException {
            list.add(user);
            Log.i(TAG,"add user:"+user);
        }

        @Override
        public User getUser(int index) throws RemoteException {
            Log.i(TAG,"get user,index:"+index);
            return list.size() > index && index >= 0 ? list.get(index) : null;
        }
    };
}
复制代码

在AndroidManifest.xml文件声明Service,以两个组件形成单独的app来体现两个进程,通过AIDL进行数据交互。在客户端通过 bindService() 来启动该服务。

<service android:name="com.gitcode.server.UserService"
    android:enabled="true"
    android:exported="true">
        <intent-filter>
            <action android:name="com.gitcode.server.userservice"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</service>
复制代码

3、客户端的实现

客户端主要是通过共同的约定(UserManger.aidl)向服务端进行请求,服务端响应客户端的请求。为了提高效率和减少出错,通过拷贝来实现客户端的AIDL文件。 将服务端的aidl整个文件拷贝到客户端的main目录下,不做任何修改

我也想聊聊Binder机制
在客户端建立与服务端User类同包的目录,并将User类拷贝过来,不做任何修改

我也想聊聊Binder机制

在Activity中绑定服务端的Service,绑定成功后进行数据交互。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client";
    private UserManager mUserManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toBindService();
    }

    private void toBindService() {
        Intent intent = new Intent("com.gitcode.server.userservice");
        intent.setPackage("com.gitcode.server");
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mUserManager = UserManager.Stub.asInterface(service);

            try {
                User user = mUserManager.getUser(0);
                Log.e(TAG, user.toString());

                mUserManager.addUser(new User("张三","北京"));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
}
复制代码

运行效果:

客户端:

我也想聊聊Binder机制

服务端:

我也想聊聊Binder机制

4、小结

客户端调用服务的方法,被调用的方法运行在服务端的的Binder线程池,同时客户端会被挂起,如果服务端方法执行耗时操作,就会导致客户端ANR,所以不要在客户端主线程访问远程服务方法。同时服务端不应该自己新建新建线程运行服务方法,因为方法会交由线程池处理,同时对数据也要做好并发访问处理。

AIDL可以说为应用层开发提供了封装,不用过多的了解Binder的机制,通过生成的UserManager.java,初步可以了解Binder的IPC机制。使用AIDL在进程之间进行数据通信,更注重的是细节和业务的实现。

上文demo地址

我也想聊聊Binder机制

三、Binder

Binder是Android系统提供的一种IPC机制。由于Android是基于 Linux 内核,因此,除了Binder以外,还有其他的IPC机制,例如Socket,共享内存,管道和消息队列等。之所有不使用原有的 IPC机制,是因为使用Binder机制,能从性能、稳定性、安全性带来更好的效果。例如,Socket是一套通用的接口,传输速率低下,适合网络传输这种情况,而管道和消息队列需要数据的两次拷贝,共享内容难以管控等。而Binder对数据只需要一次拷贝,使用C/S架构,职责明确,容易维护和使用。

通过下图可以了解到,Binder机制通过内存映射实现跨进程通信,Binder在IPC机制只是作为一个数据的载体,当进程A向虚拟内存空间中写入数据,数据会被实时反馈到进程B的虚拟内存空间。整个发送数据的过程,只从用户空间拷贝一次到虚拟内存空间。

我也想聊聊Binder机制

在Binder机制中,主要涉及到Client、Server、ServiceManger三个端,三者通过Binder进行跨进程通信,支持着Android这个大网络。它们的关系如下图。

我也想聊聊Binder机制
Server

Server进程需要注册一些Service到ServiceManger中,以对外告知其可提供的服务。例如上文AIDL中,会注册UserService,并为Client提供添加User和获取User的操作。注册的过程,Server进程就是客户端,而ServiceManger就是服务端。

Client

对Sever进程进行业务逻辑操作。通过Service的名称在ServiceManger查找对应的Service。

ServiceManager

ServiceManger集中管理系统内的所有Service,服务通过注册Service到ServiceManger的查找表中,当Client根据Service名称请求ServiceManger在查找表中查询对应的Service。

图表示三者的C/S架构,例如Client查询向ServiceManger查询Service时,Client就是客户端,而ServiceManger就是服务端。而虚线则表示两者之间通过Binder进行进程间的通信,因此通过了解一条虚线的流程,就可以知道Binder的机制。

1、Service的注册过程

通过Server进程的注册Service过程,可以了解到Binder机制的工作原理。

我也想聊聊Binder机制

BpServcieManger和BnServcieManger是客户端与服务端进程业务层辑实现的封装,而BpBinder和BBinder是IPC机制的方式。此时Server进程是客户端,ServiceManger是服务端。

1.1 ProcessState

每个进程通过单例模式创建了唯一的ProcessState对象,在其构造器中,通过 open_driver() 方法打开了/dev/binder设备,相当于Server进程打开了与内核的Binder驱动交互的通道,并设置最大支持线程数为15。binder设备是Android在内核中为完成进程间通信而专门设置的一个虚拟设备。

1.2 BpBinder和BBinder

BpBinder与BBinder都是Android与Binder通信相关的代表,两者一一对应,都从IBinder派生而来。如果说BpBinder代表客户端,那么BBinder就代表服务端,一个BpBinder通过handler标识与对应的BBinder进行交互。在Binder系统,handler标识为0代表着ServiceManger所对应的BBinder。BpBinder与BBinder并没有直接操作ProcessState打开的binder设备。

1.3 BpServiceManger和BnserviceManger

两者继承至IServiceManger,与业务逻辑相关,可以说将业务层的逻辑架构到Binder机制上。BnserviceManger从IServiceManger BBinder派生而来,可直接参与Binder的通信,而BpServiceManger通过mRemote指向BpBinder。

1.4 注册相关Service

通过上文三小节,BpServiceManger对象实现对IServiceManger的业务函数,又有BpBinder作为通信代表,下面分析一下注册的过程。

将字符串名字和Service对象作为参数传到BpServiceManger对象的 addService() 函数,该方法将参数数据打包后传递给BpBidner的 transact() 函数。业务层的逻辑到此就结束,主要作用是将请求信息打包交给通信层去处理。

在BpBinder的 transact() 函数调用了IPCThreadState对象的 transact() 函数,所以说BpBinder本身没有参与Binder设备的交互。每个线程都有一个IPCThreadState对象,其拥有一个mOut、mIn的缓冲区,mOut用来存储转发Binder设备的数据,而mIn用来接收Binder设备的数据。通过 ioctl 方式与Binder设备进行交互。

1.5 小结

通过上文Service的注册过程,分析了Binder的机制。Binder只是通信机制,业务可以基于Binder机制,也可以基于其他IPC方式的机制,也就是上文为啥有BpServiceManger和BpBinder。Binder之所以复杂,是Android通过层层的封装,巧妙的将业务与通信融合在一起。主要还是设计理想很牛逼。

我也想聊聊Binder机制

2、ServiceManger

通过1小节的分析,是否应该也有一个类继承自BnServiceManger来处理远方请求呢?

很可惜的是在服务端并没有BnServiceManger子类来响应远程客户端的请求,而是交给了ServiceManger来处理。

2.1 成为Service管理中心

ServiceManger通过binder_open函数打开binder设备,并映射内存。通过handler等于0标识自己,让自己成为管理中心,所有service向ServiceManger注册时,都是通过handle标识为的0的BpBinder找到ServiceManger对应的BBinder,ServiceManager会保存要注册的Service的相关信息,方便Client查找。并不是所有的Service都可以在ServiceManger注册,如果Server进程的权限不够root或system,那么需要在allowed添加相应的项。

2.2 ServiceManger集中管理带来的好处

  • 统一管理,施加管控权
  • 通知字符串名称查找Service
  • Server进程生命无常,通过ServiceManger,Client可以实时知道Server进程的最行动态。

3、Client

Client想要使用Server进程提供的Service,又该进行哪些步骤呢?

3.1 查询ServiceManger

Client想要得到某个Service的信息,就得与ServiceManager打交道,通过调用getService()方法来获取对应Service信息。Client通过服务名称向ServiceManger查询对应的Service。如果Service未注册,则循环等待直到该Service注册;如果已注册,则会对应封装了一个能与远程Service通信的BpBinder的BpXXXService,通过该Service,Client客户调用相关业务逻辑函数。

3.2 请求信息的处理

Client调用的业务函数,莫非就是将请求参数打包发送给Binder驱动,BpBinder通过handler的值找到对应端的Service来处理。

在1.4小节中,说到IPCThreadState对象,在其executeCommand函数中,通过调用实现了BnServiceXXX的对象onTransact函数,直接定位到业务层。这就是在AIDL中,为什么在onTransact()函数中处理响应数据。

四、总结

通过对Binder机制的学习,了解Android是如何通过层层封装将Binder机制集成要应用程序,对Binder机制有一个较深入的理解。可以通过第Java层AIDL的使用,加深对Binder机制的理解。

个人水平有限,有误请帮忙勘正,谢谢大佬。喜欢就帮忙点个赞呗。

逛逛GitHub

参考资料:

深入理解Android 卷一


以上所述就是小编给大家介绍的《我也想聊聊Binder机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Eloquent JavaScript

Eloquent JavaScript

Marijn Haverbeke / No Starch Press / 2011-2-3 / USD 29.95

Eloquent JavaScript is a guide to JavaScript that focuses on good programming techniques rather than offering a mish-mash of cut-and-paste effects. The author teaches you how to leverage JavaScript's......一起来看看 《Eloquent JavaScript》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具