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