我正在参与「启航计划」
1、什么是binder
binder是android framework供给的,用于跨进程办法调用的机制,具有安全高效等特色。
咱们知道,在 Android 体系中,每个应用程序都运行在一个独立的进程中,各个进程之间需求进行数据交换和调用,以完成各种功能。为了完成这个目的,binder应运而生。
2、binder的前史
android基于linux内核,linux供给了十分多的跨进程机制。 为什么android要运用binder而不运用linux已有的ipc机制呢,这个选择在android内部团队中有过争论。 部分工程师以为binder内部的线程等待带来了体系开销,同时没有显示出更好的用途。 但最后binder还是凭仗其优势胜出了。 其实,binder是部分android工程师在PalmSource作业时开源的一种ipc机制,担任开发ipc的工程师直接在此binder上进行了移植和开发。
在《Android传奇》一书中有介绍binder的相关前史。
3、binder根本运用
binder是一种架构,这种架构供给了服务端接口、binder驱动和客户端三个模块。
客户端经过binder长途调用服务端接口,binder驱动担任完结这次长途调用。 在android framework中供给了Binder类,一个类如果扩展Binder类,那么该类就有供给长途服务的才能,该类目标一旦创立,其内部就会创立一个隐藏的线程,用来接收binder驱动发送的音讯,然后调用Binder类的onTransact()办法。
binder驱动从体现上看,便是在客户端和服务端传递各种音讯,以完结跨进程调用。 在任意一个服务端binder目标创立时,同时也会在binder驱动中创立一个对应的mRemote目标,该目标也是binder类型。 客户端便是经过mRemote目标来访问长途服务的,相当于经过了一层署理。 这种机制供给了更好的安全性。
客户端经过获取长途服务在binder驱动中对应的mRemote目标,经过调用mRemote.transact办法然后完成对长途服务的调用。
从整个架构上看,便是服务端供给binder,binder驱动担任转发音讯,客户端获取binder引证并调用。 粗略看很简单,细节是魔鬼。
摘录一个书中的例子,从demo了解binder的调用进程。 比如要供给一个音乐播映的长途调用, 能够供给一个MusicPlayerService,代码如下:
public class MusicPlayerService extends Binder {
public void startPlay(String filePath) {
}
public void stop() {
}
@Override
public void onTransact(int code, Parcel data, Parcel reply, int flag) {
// 这个办法很重要,由binder驱动调用
// 依据约好的code值别离调用不同的事务办法,此处是startPlay和stop
if (code == 0x100) {
this.startPlay(file_path)
} else if (code == 0x101) {
this.stop()
}
}
}
如上MusicPlayerService就能够供给了长途调用服务了。 可是客户端怎么运用呢。经过binder架构知道,要想长途调用,必须获取长途服务在binder驱动中对应的binder署理目标。此处假设已经获取到相关的引证mRemote,那么客户端便能够如下调用。
IBinder mRemote = null;
String filePath = "xxx";
int code = 0x100;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain(); // 接收长途调用的成果
data.writeString(filePath);
mRemote.transact(code, data, reply, 0)
如上便完结了客户端对服务端的长途调用。 从这个demo就能够看出跨进程调用其实便是要经过Binder驱动来中转一下调用。 曾经没看过这个示例时老是不了解aidl生成的规矩,不断的复习也总是记不住规矩。可是经过这个示例一下就明白了这个进程,在了解的基础上再去手动编写binder类就水到渠成了(尽管一般也用不到手动去写binder类)。
现在就只剩怎么获取长途服务对应的binder目标了。 为了简化这个进程,android frameworks经过service供给这个才能。 在service的生命周期办法中有一个onBind办法回来IBinder目标,办法签名如下:
override fun onBind(intent: Intent?): IBinder? {
在调用context.bindService办法进行绑定时,要求传入一个ServiceConnection的目标,在服务绑定成功时会回调onServiceConnection办法,一并传回服务端的Binder署理目标。 如下:
Intent it = new Intent(this, xxxService.class);
context.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 此处就拿到了长途服务对应的binder了,客户端经过此binder就能够调用长途服务的办法了。
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Service.BIND_AUTO_CREATE);
仔细想想,应用开发好像也只有这么一条路获取服务端的binder。 可是开始咱们界说的MusicPlayerService并不是一个真实的service,无法运用bindService办法,故需求改造一下,让MusicPlayerService承继Service类。 如下:
public class MusicPlayerService extends Service {
private XXXBinder mBinder;
@Override
public IBinder onBind(Intent intent) {
return mBinder; // 此处回来服务端的binder
}
public void startPlay(String filePath) {
}
public void stop() {
}
// 因为android应用开发上只能经过service进行长途调用,故在service内部类创立binder,便利调用service界说的事务办法。
public xxxBinder extends Binder {
@Override
public void onTransact(int code, Parcel data, Parcel reply, int flag) {
// 这个办法很重要,由binder驱动调用
// 依据约好的code值别离调用不同的事务办法,此处是startPlay和stop
if (code == 0x100) {
startPlay(file_path)
} else if (code == 0x101) {
stop()
}
}
}
}
因为java不能多承继,承继了service类就不能承继Binder类了。 那么能够在MusicPlayerService类内部构建一个内部类,承继binder,并经过onBind回来binder目标给客户端。 这个代码架构便是运用binder的根本雏形了。可是,在日常开发进程中,为了扩展性和解耦,一般将事务办法经过接口笼统出来。 如下笼统出播映接口。
public interface IPlayer {
public void startPlay(String filePath);
public void stop();
}
经过上面的代码能够发现,播映的完成放在XXXBinder类或者MusicPlayerService都是可行的。 为了便利binder调用事务办法,尝试用binder完成接口并完成事务。 改造后的代码如下:
public class MusicPlayerService extends Service {
private XXXBinder mBinder;
@Override
public IBinder onBind(Intent intent) {
return mBinder; // 此处回来服务端的binder
}
// binder完成了IPlayer事务接口
public xxxBinder extends Binder implemention IPlayer {
@Override
public void onTransact(int code, Parcel data, Parcel reply, int flag) {
// 这个办法很重要,由binder驱动调用
// 依据约好的code值别离调用不同的事务办法,此处是startPlay和stop
if (code == 0x100) {
this.startPlay(file_path)
} else if (code == 0x101) {
this.stop()
}
}
public void startPlay(String filePath) {
Log.i("test", "startPlay");
}
public void stop() {
Log.i("test", "stop");
}
}
}
至此,运用binder的代码结构就较为明晰了。以上demo一步步的改造,其实便是为了更加深刻的了解binder的运用,并向aidl靠拢,然后更好的了解aidl生成的代码。 从上代码能够看出,运用binder的代码接口根本是固定的,所以android framewrok供给了一个aidl的东西来简化这个进程。
4、aidl的运用
在对应模块上右键选择aidl,会弹出创立aidl的对话框,输入名字as会自动生成对应的aidl文件。 在aidl文件中加入自己的事务接口即可。 build一下as就会依据aidl自动生成相应的接口类和binder类,具体文件此处省掉。
需求注意的是,aidl中只支撑Parcelable目标和原子类,如果有自界说的类需求跨进程访问时需求完成Parcelable接口。
客户端经过如下代码获取接口目标并完成调用。
class MusicClient {
private var mIMusicPlayer: IMusicPlayer? = null
private var mServiceConnection = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// 这个办法中供给了是本地通讯还是跨进程通讯的判别
// 这里就客户端进程就获取了长途服务的binder引证了。 aidl东西生成了Stub及Proxy类封装了直接经过binder.transact调用的逻辑,让开发者只需和接口交互即可。 不用关怀binder交互的细节。
mIMusicPlayer = IMusicPlayer.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
fun bindMusicService(context: Context) {
val intent = Intent(context, PlayerService::class.java)
context.bindService(intent, mServiceConnection, Service.BIND_AUTO_CREATE)
}
}
注意在运用aidl时,如果客户端和服务端在不同的工程时,两头的aidl文件要是相同的。实践中能够直接拷贝。
5、binder架构
此处摘录一张binder架构图
从这张图就能够明晰看到客户端、驱动及服务端的职责,也能更好的了解binder的交互进程。这么看下来,感觉binder驱动其实便是担任对音讯进行了转发,同时对交互的进程进行了一定控制。
6、总结
1、一个类要想序列化就要完成Serializable或Parcelable接口,同理一个类要想供给跨进程服务,就必须承继binder类。 binder就像一个符号类相同,只需承继了,就有资格在进程间通讯了。
2、一个binder跨进程的通讯包含了客户端、服务端和binder驱动三方。
3、在应用开发中首要经过aidl东西、Service及Context.bindService完成跨进程访问。
4、aidl是一个命令行东西,协助生成跨进程调用的样板代码。
7、参考
1、《Android传奇》
2、《Android内核分析》
3、《Android进阶解密》