title: IPC_Binder_java_1
date: 2017-01-03 21:30:55
tags: [IPC,Binder]
categories: [Mobile,Android]
---
由于 Binder 很复杂,这个分多篇展开,目前先将零碎的知识整合,在后面几篇进行总结.
Binder 用于进程间通信,而 Handler 消息机制用于同进程的线程间通信。
Binder 的英文涵义是别针,回形针的意思。
在 Android 中 Binder 的存在是为了完成进程间的通信,将进程"别" 在一起。比如说:普通应用可以调用播放器提供的服务:播放、暂停、停止等功能。
Binder 是工作在 Linux 层面,属于一个驱动,只是这个驱动是不需要硬件的,或者说是基于操作系统的一小块内存。从线程的角度来讲,Binder 驱动的代码是运行在内核态的,客户端程序调用 Binder 是通过系统调用完完成。
Binder 框架提供 服务端接口、Binder 驱动、客户端接口 三个模块。
从服务端的角度来说,一个 Binder 服务端实际上就是一个 Binder 类的对象,该类一旦创建,内部就会启动一个隐藏线程。该线程接下来就用于接收 Binder 驱动发送来的消息,收到消息之后,会执行到 Binder 对象中的 onTransact 方法,在这个方法中,根据不同的参数,执行不同的服务代码。因此,要实现一个 Binder 服务,就必须重载 onTransact 方法。
在 onTransact 方法中,会获取传递进来的参数,将其转换成服务函数的参数。onTransact 参数的来源于 客户端的调用 transact 方法。所以,如果 transact 方法的参数有固定的格式输入,那么 onTransact 就会有相应的固定格式输出。
从 Binder 驱动的角度来说。任何一个服务端的 Binder 对象被创建的时候,都同时会在 Binder 驱动中创建一个 mRemote 对象,这个对象也是 Binder 类。客户端想要访问远程服务的时候,都是通过这个 mRemote 对象。
从以上的叙述中,可以看出,对应用开发者来说,客户端似乎是在直接调用了远程服务对应的 Binder ,而事实上,则是通过 Binder 驱动中的 Binder 对象,不同的是, Binder 驱动中的对象不会额外产生一个线程。
设计 server 端只需要新建一个继承 Binder 的 service 即可,当启动这个 service 的时候,在 ddms 中的 thread 会发现多了一个 Binder thread 。
定义完 service ,接下来需要重载 onTrasact 方法,并从 data 变量中读取客户端传递进来的参数。 假如,这里有很多参数,那么怎么知道参数的顺序呢?所以,这个需要一个双方的约定。
方法的第一个参数 code 是用来标记不同的服务端函数的。
如果想要返回结果,则在 reply 中调用相关的函数写入即可。
对于客户端要想使用服务端的服务函数,则必须先获取服务端在 Binder 驱动中对应的 mRemote 对象。在获取到该对象之后,就可以调用该变量的 transact 方法。
public final boolean transact(int code, Parcel data, Parcel reply, int flags)
data 是传递给服务端的数据,远程服务函数的参数,都是从这个 data 中取的。
data 中能放的类型都是常用的原子类型,String,int ,long 等,当然也包括实现了 Parcelable接口的类。
这里向 data 写入的数据的顺序,必须和 onTransact 取参数的顺序保持一致,需要实现约定好。
当调用客户端调用远程方法,经由 mRemote 调用 transact 的时候,客户端线程进入 Binder 驱动, Binder 驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹数据。
当服务端 service 执行 onTrasact 的时候,就可以对包裹 data 进行拆解,然后根据参数执行相应的 服务函数,执行完之后,会将执行的结果放入 reply 中。
当这一切都执行完之后,服务端会向 Binder 驱动发送一个 notify 的消息(客户端线程在调用 transact 的时候,客户端线程会被挂起),从使得客户端线程从 Binder 驱动代码区返回到客户端代码区。
对于最后一个参数 flags ,表示的是 IPC 调用的模式,分为:双向,用0 表示,含义是服务端执行完之后会返回一定的数据;还有一种是单向,用1 表示,含义是不返回任何数据。
同样的,返回到结果都是在 reply 中,客户端从这个 reply 中取的数据,这部分顺序也必须实现约定好。
在编写 Binder 服务端和客户端的过程中,会伴随着两个问题。
使用 Binder 的原因是想提供一个全局的服务,所谓的全局,意思是系统中的任何程序都可以访问 。很明显,这个应该属于操作系统需要提供的基本功能之一,所以有个方法就是 service。
无论是否使用 service类,都需要解决上面的两个问题。
当然完全可以不使用 service 类,而是仅仅基于 Binder 类编写服务程序,然而这个只是一部分。具体来说,可以仅仅使用 Binder 类扩展系统服务,对于客户端服务则必须是基于 service 类来编写的。系统服务是指那些通过 getSystemService 方法获取的服务,而客户端服务是指应用程序提供的自定义服务。
也就是说,扩展系统服务的时候,可以完全只使用 Binder 类;而对于客户端的服务则必须基于 service。
看下几个启动 service 相关的方法,这些方法在 android.app.ContextWrapper 类中。
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
这个方法很熟悉,就是一个 启动服务的方法,然后启动之后,客户端并不能拿到服务端的 Binder 引用,因此并不能调用服务端的任何服务。
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return mBase.bindService(service, conn, flags);
}
public interface ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service);
public void onServiceDisconnected(ComponentName name);
}
bindService 方法第一个参数是启动 service 的intent ,第二个参数是一个接口,接口中有个方法叫 onServiceConnected 这个方法含有两个参数,第二个参数就是 Binder 。
具体的运行过程中,当客户端请求启动 service 的时候,请求就会通过 Ams 发出,若 service 正常去懂了,那么 Ams 就会远程调用 ActivityThread 类中的 ApplicationThread 对象,调用的参数就包含了 service 的 Binder 对象的引用,然后在 ApplicationThread 中回调 bindService 的第二个参数 ServiceConnection 的方法 onServiceConnected ,将 Binder 引用传递回客户端,这样客户端就拿到了远程服务的 Binder 对象引用,在实际操作中,常常可以这个 Binder 对象引用设置成一个全局变量,可以在客户端的任何地方都可以访问到。
在数据传递的过程中,需要实现约定好服务函数所对应的 code 的 int 值,需要约定好参数的写入顺序。在 Android 中的 AIDL 就是这么个工具。
AIDL 可以将一个 AIDL 文件转换成一个 Java 类文件,同时重载 transact 和 onTransact 方法。关于服务函数对应的 int 值和参数的读写书序,都统一做了处理。这样,开发者只需要专注于服务代码本身了。
可以看得出来,AIDL 并非是必须的,只是一个工具。
interface ICompute {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
int add(int a, int b);
}
一般情况下,第一个字母是 I,这样是为了程序风格的统一,后面的 Compute 是服务的类名,AIDL 工具会以这个服务的名字生成 Java 类。( 当然这个是默认的,相应的也是可以修改的,具体的另行参照说明。 )
aidl 文件中可以引用其他的 Java 类,但是需要遵循以下要求:
运行 AIDL 工具之后,生成的文件中,包含了 Java interface,proxy,stub类。
本篇最后,放一张图进行总结.
感谢以下的文章以及其作者和翻译的开发者们,排名不分先后
原文:https://www.cnblogs.com/jnienv/p/10575595.html