進程間通信--android binder

不在同一個進程的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);

 }








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章