进程间通信--android binder

秦城
2023-12-01

不在同一个进程的Activity或者Service是如何通信

在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动程序,其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。Service Manager和Binder驱动已经在Android平台中实现好,开发者只要按照规范实现自己的Client和Server组件就可以了。

使用Binder 服务

C++层和Java层使用Binder 服务的方式基本一样,包括函数的接口类型都相同。

使用Binder服务首先要得到他的引用对象,例如,要得到CameraService的引用对象

sp<IServiceManager> sm=defaultServiceManager();

sp<IBinder> Binder = sm->getService("media.camera");

defaultServiceManager()方法用来得到ServiceManager服务的引用对象。ServiceManager的引用对象是直接用数值0作为引用号构造出来的。ServiceManager的getService()方法的作用时查找注册的Binder服务。如果getService()找到对应名称的服务,他会返回服务的IBinder对象,否则返回NULL。

应用需要的是Binder的代理对象,因此在使用前需要把引用对象转换成代理对象。

ICameraService service = ICameraService.Stub.asInterface(binder);

Binder 中提供了asInterface()方法来完成这种转换,asInterface()方法会检查参数中的IBinder对象的服务类型是否相符,如果不符,返回NULL。

C++ 和Java 互调服务的方法就是直接使用服务的引用对象。

例:C++调用Java的服务ActivityManagaerService的CheckUriPermission()方法:

int checkUriPermission(Uri uri, int pid, int uid, int mode)

客户端中的调用代码可以这样写:

sp<IBinder> binder =defaultServiceManager()->getService("activity");

Parcel data = Parcel.obtain();

Parcel reply = Parcel.obtain();

data.writeInterfaceToken("android.appIActivityManager");

uri.writeToParcel(data, 0);

data.writeInt(pid);

data.writeInt(uid);

data.writeInt(mode);

binder->transact(54, data, reply, 0);//54代表了方法checkUriPermission()

reply.readException();

int res = reply.readInt();

这段代码使用Parcel类打包参数,然后调用IBinder的函数transact()来调用远程服务。这种代码不常用。函数号在在服务端的定义中可能会随着Android版本的升级变化。

用Java 开发Binder服务:

写一个简单的组件Service,里面包含一个匿名的Binder服务。

第一步,编写AIDL(Android Interface Describation Language,是一种Anddroid提供的用来简化Binder编程的脚本语言,开发人员只需要在AIDL文件中定义出方法接口,AIDL解释器能自动生成服务器和客户端需要的java代码,这也是java服务比C++服务容易开发的原因)文件,定义两个简单的方法,get()和set()。

interface ITestService {

int get();

void set(int val);

 }

第二步,编写Service代码:

public class TestService extends Service{      //TestService 需要从Service 类继承,并且必须重载 onBind() 方法,并在其中返回Binder服务的实体对象mInstance

int mValue = 0;

private final ITestService.Stub mInstance = new ITestService.Stub() { // 是真正的Binder 服务类,他是通过前面的aidl文件自动生成的,因此这里还需要继承ITestService.Stub,并重载他的方法,并实现他们的功能

public int get (){

return mValue;

 }

public void set (int val){

mValue = val;

 };

@override 

public IBinder onBinde(Intent intent ){

return mInstance;

 }

 }

}

第三步,在AndroidManifest.xml中加入服务的声明:

<service android:name =".TestService">

<intent-filter>

<action android:name = "com.android.ITestService"/>

</intent-filter>

</service>

这里加入声明的作用是为了让Framework 能通过Intent 来找到Service。

解析名称的模块--ServiceManager的作用:从函数svcmgr_handler()代码来看,ServiceManager提供了三种服务

do_add_service()注册Binder服务

该函数的流程是首先检查调用进程是否有权限注册,接着检查看需要注册的服务是否已经存在,如果存在,就把原来Binder服务在驱动中的引用计数减一,不存在则创建一个新的svcinfo结构,把服务的名称信息填入结构中,然后把结构加入到服务列表svclist中,最后通知内核爸Binder服务的引用计数加一,并且需要接受该Binder服务的死亡通知。

检查进程权限的函数 scv_can_registe()函数,Android 5.0 之前规则,如果进程属于root 或 system,可以注册任意名称的服务,否则只容许在allowed 表定义的进程表中规定的服务。为什么要这张表?因为Android系统里的一些专业的服务,如通信,多媒体等,放在各自的进程中比较合适。但是Android 5.0 以后采用SELinux 的权限来代替这张表。函数check_mac_perms_form_lookup()的代码。

do_find_serve()查询Binder服务,

主要工作是搜索列表,返回查找的服务。allowed_isolated 属性来控制是否允许一般的用户进程来使用其服务

获取Binder服务列表

源码目录位于frameworks/native/cmds/serviceManager下,bind.c 用来实现简单的Binder通信功能,service_manager.c用来实现ServiceManager上层逻辑。

匿名共享内存 ashmem

mmap是Linux中最为大家熟知的内存共享方式,通过打开同一个文件,并且使用MAP_SHARED 标志来调用mmap()函数,两个进程就能共享一片内存空间了。如果某个页面的物理内存不需要了,mmap没有办法单独释放,Android 开发了匿名内存共享机制ashmem,建立在mmap基础上,提供了pin和unpin两个io操作,能够部分释放内存空间的物理内存。ashmem并不能用于任意两个进程间的内存共享,必须是在通过Binder建立了联系的两个进程之间。

ashmem的用法:Android提供了一组使用ashmem的函数。

头文件ashmen.h 位于目录 system/core/include/libcutil/下,实现代码ashmem-dev.c位于/system/core/libcutil 中,ashmen的使用步骤如下:

1.首先创建一个共享区域

//ashmem_create_region

主要工作是:

打开设备文件“/dev/ashmem”, 得到一个文件描述符fd

如果参数name不是NULL,通过ioctl操作ASHMEM_SET_NAME来设置名称

通过ioctl操作ASHMEM_SET_SIZE来设置内存大小

int ashmem_create_region(const char *name, size_t size){

int fd, ret;

fd = open(ASHMEM_DEVICE, O_RDWR);

if (fd < 0) return fd;

if(name){

char buf[ASHMEM_NAME_LEN];

strlcpy(buf, name, sizeof(buf));

ret = ioctl(fd, ASHMEM_SET_NAME, buf);

if(ret < 0)  goto  error;

 }

ret = ioctl(fd, ASHMEM_SET_SIZE, size);

if(ret < 0)  goto error;

error: 

close(fd);

return ret;

 }

2.得到文件描述符后,使用mmap来分配内存,例如:

void* base = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

3.如果希望改变内存的属性:

int ashmen=m_set_port_region(int fd, int port){

return ioctl(fd,ASHMEM_SET_PORT_MASK, port);

 }

其中参数和mmap的内存属性参数一致,可以是PORT_READ, PORT_WRITE, PORT_EXEC等。

4.解锁部分内存:

int ashmem_unpin_region(int fd, size_t offset, size_t len){

struct ashmem_pin pin = {offset, len};

return ioctl(fd, ASHMEM_UNPIN, &pin);

 }

解锁后,这部分内存会在内存不足时被系统回收。

5.如果需要,可以把解锁的内存块重新锁定

int ashmem_pin_regin(int fd, size_t offset, size_t len){

struct ashmem_pin pin = {offset, len};

return ioctl(fd, ASHMEM_PIN, &pin);

 }

6. 如果要获取内存块的大小:

int ashmem_get_size_regin(int fd){

 return ioctl(fd, ASHMEM_GET_SIZE, NULL);

 }








 类似资料: