前言
做Android开发,迈入高级必须会的技术很多,而且肯定包括Binder技术,关于Binder的了解,笔者最开始只是使用AIDL工具开发接口,生成Service的服务方法,对于其内部的实现细节不太了解。从书本上或者搜索看了好几遍,每次都是似懂非懂,然后日子久了,连那点印象也不见了。所以最近的这次综合了解了这个技术后,决定将自己的理解记录下来,一方面巩固理解,一方面分享给需要的朋友。
两个对象能直接互相访问的前提是这两个对象都存在于相同的内存地址空间中,如果两个对象位于两个不同进程,则不能直接互相调用。Binder 是Android中的一种跨进程通信方式,即IPC(Inter-process Communication)一种。首先,我们学习开发最开始知道的知识点,线程是CPU的最小调度单位;进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,进程可以包含多个线程。理论是这么说,从客观认识上,一个普通的app就是一个进程,很明显两个app之间不能够直接通信,如果需要,那就要用到IPC的各种方式,这里只讲binder,或者说Android中的Service,即Android四大组件之一,Binder主要用在service中。
Binder主要模块
图1, Binder框架
Binder主要分为Binder服务端、Binder客户端和Binder驱动。
首先Binder服务端,可以看做是一个Binder类的对象,该对象创建后,会接收Binder驱动发送的消息,即在收到消息的回调中处理,执行服务代码。这个回调即是onTransact()方法。
从翻译来看,transact是办理、处理的意思,这里就可以认为,binder驱动这个中介把客户端的需求发到了服务端,服务端即可以根据回调信息进行处理。那既然有收,肯定客户端要发数据,服务端才能收到。
最后看Binder客户端,客户端要访问远程服务,调用服务接口,那么就必须获取远程服务在Binder对象中对应的mRemote引用,或者可以直接认为获取Binder。然后调用其transact()方法,将客户端要调用的函数和参数传递到服务端,当然是经由Binder驱动中介传到服务端。
由于Binder驱动位于内核空间,且较底层,所以我们不用太深入了解其原理,只需把它看做一个黑盒,我们只看接口输入输出,作为和服务端传递信息的信使就可以,等学有余力我们继续深挖。这里可以有个大概的认识,其为字符型设备,用户可以从/dev/binder设备文件节点上通过open和ioctl文件操作函数与Binder driver通信,其主要负责Binder通信机制的建立以及在进程间传递和Binder引用计数管理、数据包传输等。
Demo示例
这里以一个aidl的demo为示例,其链接为binderdemo,其中此project中的app module为binder的server端,有MediaService作为服务器,添加的AIDL接口。然后binderclient module为binder客户端。
// IMediaService.aidl
package com.example.binderserver;
import com.example.binderserver.MediaInfo;
// Declare any non-default types here with import statements
interface IMediaService{
void startplay(in MediaInfo info);
void stop();
void search(String name);
}
其中的MediaInfo的aidl源码为:
// MediaInfo.aidl
package com.example.binderserver;
// Declare any non-default types here with import statements
parcelable MediaInfo;
AIDL中支持的数据类型有:
1.基本数据类型:int、long、char、boolean、double等;
2.String和CharSequence;
3.ArrayList,且其每个元素都必须是支持的数据类型;
4.HashMap,且其每个元素都是支持的数据类型;
5.Parcelable,即所有实现了parcelable接口的对象;
6.AIDL,即所有AIDL接口本身。
所以根据第5条,MediaInfo非其他AIDL支持的类型,自定义类必须继承parcelable。
而parcelable是Android序列化中最重要的方式,在跨进程通信中很重要,总之,就是重要。 想对其有深入了解的读者可以参考我的另一篇文章:一文看懂Android中的序列化
接下来我们继续说服务端和客户端,为了读者更快了解框架全貌,这里先把代码附录上。
服务端的代码如下,
public class MediaService extends Service {
private static final String TAG = "MediaService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return (IBinder) mBinder;
}
private IMediaService mBinder = new IMediaService.Stub() {
@Override
public void startplay(MediaInfo info) throws RemoteException {
Log.d(TAG, "startplay mediaInfo =" + info.toString());
}
@Override
public void stop() throws RemoteException {
Log.d(TAG, "service stop ");
}
@Override
public void search(String name) throws RemoteException {
Log.d(TAG, "service search name=" + name);
}
};
}
客户端的代码如下:
public class MainActivity extends Activity {
private static final String TAG = "binderclient";
ServiceConnection conn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected");
IMediaService binder = IMediaService.Stub.asInterface(iBinder);
try {
Log.d(TAG, "onServiceConnected search invoke");
binder.search("忘情水");
Log.d(TAG, "onServiceConnected startplay invoke");
binder.startplay(new MediaInfo("夜空中最闪亮的星", "xxx", "逃跑计划"));
Log.d(TAG, "onServiceConnected stop invoke");
binder.stop();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(TAG, "onServiceDisconnected");
}
};
Intent intent = new Intent();
intent.setAction("example.bindertest");
intent.setPackage("com.example.binderserver");
bindService(intent, conn, BIND_AUTO_CREATE);
}
@Override
public void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
源码都已附上,然后由于AIDL是Android提供的Binder工具,并不是真正的源码,如您下载了github源码并编译成功,则会在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/binderserver目录发现IMediaService.java源码,public interface IMediaService extends android.os.IInterface,可以看出AIDL的方法是interface的类,编译生成的自然也是interface类, 由于其继承IInterface,由于这是个接口类
public interface IInterface {
IBinder asBinder();
}
所以也需要实现其asBinder方法。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.binderserver;
// Declare any non-default types here with import statements
public interface IMediaService extends android.os.IInterface
{
/** Default implementation for IMediaService. */
public static class Default implements com.example.binderserver.IMediaService
{
@Override public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException
{
}
@Override public void stop() throws android.os.RemoteException
{
}
@Override public void search(java.lang.String name) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService
{
private static final java.lang.String DESCRIPTOR = "com.example.binderserver.IMediaService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.binderserver.IMediaService interface,
* generating a proxy if needed.
*/
public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
return ((com.example.binderserver.IMediaService)iin);
}
return new com.example.binderserver.IMediaService.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_startplay:
{
data.enforceInterface(descriptor);
com.example.binderserver.MediaInfo _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.binderserver.MediaInfo.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.startplay(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_stop:
{
data.enforceInterface(descriptor);
this.stop();
reply.writeNoException();
return true;
}
case TRANSACTION_search:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
this.search(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.binderserver.IMediaService
{
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 startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((info!=null)) {
_data.writeInt(1);
info.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_startplay, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().startplay(info);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void stop() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().stop();
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void search(java.lang.String name) 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.writeString(name);
boolean _status = mRemote.transact(Stub.TRANSACTION_search, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().search(name);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.example.binderserver.IMediaService sDefaultImpl;
}
static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
public static boolean setDefaultImpl(com.example.binderserver.IMediaService impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.binderserver.IMediaService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
public void stop() throws android.os.RemoteException;
public void search(java.lang.String name) throws android.os.RemoteException;
}
方法含义
划重点啦,要理解Binder及AIDL使用,以下的函数定义必须牢记,就像知识字典,如果看源码有不懂的就来查下字典,如果牢记了以下几点,相信你肯定对Binder有了基本的认识。
- DESCRIPTOR: Binder的唯一标识,一般用当前Binder的类名表示。这里即“com.example.binderserver.IMediaService”
- Stub:是一个abstract类,即抽象类,继承了Binder类,并实现IMediaService接口,主要在服务端使用。既然继承了Binder,我们就可以把Stub看做Binder。定义为抽象类是因为具体的服务函数需要由程序员实现。看demo编译生成的IMediaService源码,
public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService
由于IMediaService是接口类,所以Stub类implement了IMediaService类就需要实现其接口, 服务端需要程序员手动去实现这些接口。
public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
public void stop() throws android.os.RemoteException;
public void search(java.lang.String name) throws android.os.RemoteException;
然后Stub类内部重载onTransact方法。由于继承的Binder类,而Binder类public class Binder implements IBinder又继承接口public interface IBinder,所以服务端函数Stub即需要实现Binder接口onTransact和其中带过来的请求,即如下第5条。这里onTransact处理Binder传过来的客户端请求后最后又调用的即是IMediaService的接口函数,即需要程序员实现的部分,最后返回。
4. asInterface(android.os.IBinder obj):将服务端的Binder对象转换为客户端需要的AIDL接口类型的对象,此转换区分进程,如果客户端和服务端位于同一进程,则此方法返回服务端对象本身即不需要经过IPC调用,否则返回系统封装的Stub.proxy对象。可以看到Stub类的asInterface的源码中,有 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);这个用来判断是否是本地接口,即本进程的方法,接下来判断iin如果不为空且是IMediaService的实例时,直接返回。否则调用Stub的Proxy方法。如下图所示。
/**
* Cast an IBinder object into an com.example.binderserver.IMediaService interface,
* generating a proxy if needed.
*/
public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
return ((com.example.binderserver.IMediaService)iin);
}
return new com.example.binderserver.IMediaService.Stub.Proxy(obj);
}
- asBinder:返回当前的Binder对象。
- onTransact:运行在服务端Binder线程池,客户端的数据请求通过系统底层封装后回调到此方法来处理,其中
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
参数code表示客户端调用方法值,data中是客户端调用方法的参数,reply中是客户端调用方法的返回值。 由于data中包裹的数据是在客户端的transact方法写入,其顺序由AIDL工具定义,自然在onTransact中,AIDL工具也根据定义的数据包裹顺序解出来进行处理。 - enforceInterface:为了某种校验,与客户端Proxy中的writeInterfaceToken对应。
- Proxy:运行在客户端,作为客户端访问服务端的代理,客户端在对应的接口函数内创建Parcel对象_data和_reply,想要深入了解下parcel也可以查看我的另一篇关于序列化的解读,都是类似的。一文看懂Android中的序列化,序列化中封装的参数信息也是按顺序约定的。
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
并将要调用的方法所需的参数信息写入_data,然后通过Binder的远程调用mRemote封装调用方法的code和_data,_reply,(_data是要传递给远程Binder服务的包裹(Parcel),其中放入code对应方法所需参数,然后)发起跨进程调用;同时客户端的当前线程挂起,等待服务端的onTransact方法调用结束后,把执行结果放入_reply中,然后服务端向Binder驱动发送notify消息,从而客户端线程能从Binder驱动中返回到继续执行客户端代码。
然后这里的方法code,用于标识客户端想调用服务端的哪个函数,即双方约定好一组int值,不同的int值代表不同的服务端函数。客户端transact的code和服务端的onTransact的code是对应的。
static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
测试结果
读者可以根据需要下载demo源码自己测试,地址为binderdemo,根据上述章节的讲解,读者也可以测试在同一进程中看是否
客户端的执行结果打印
2020-02-06 16:59:39.827 7447-7447/com.example.binderclient D/binderclient: onServiceConnected
2020-02-06 16:59:39.828 7447-7447/com.example.binderclient D/binderclient: onServiceConnected search invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected startplay invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected stop invoke
同时服务端的执行结果:
2020-02-06 16:59:39.828 6519-6532/com.example.binderserver D/MediaService: service search name=忘情水
2020-02-06 16:59:39.829 6519-6531/com.example.binderserver D/MediaService: startplay mediaInfo =com.example.binderserver.MediaInfo@54956ae
2020-02-06 16:59:39.829 6519-7005/com.example.binderserver D/MediaService: service stop
结语
本文结束了,希望大家能对Binder和AIDL有个不错的了解,如果使用过AIDL的读者应该更加加深了印像,但是建议收藏下,过一段时间复习下,否则不常用时又会遗忘。对于初学者,如果不能很快的理解,可以先记忆住上述章节的方法含义部分,然后加以demo练习,如不能记住,容易捡了西瓜丢了芝麻,做到胸中有丘壑。这样在练习或者实操时按图索骥就理解了。
当然本文只简单的通过例子解析了其中的部分方法和原理,Android中还有其他例子可讲,比如Android系统的ServiceManager,其是系统所有原生Service的总管家,它也是个BinderServer,想继续深入的同学可以继续研究下其源码,想查看系统源码的可以参考:如何查看Android系统源码
喜欢或者对你有帮助的同学帮忙点个赞吧,文中参考了书中的知识和自己的理解,如有错误,欢迎留言指正交流。
参考文献:
《Android开发艺术探索》
《Android内核剖析》
《Android源码设计模式解析与实战》