一、引言:
作爲Android系統中使用最多的IPC通信機制,binder確實非常複雜,在網上看了很多人的博客,還有各種書籍中對binder的介紹,各有各的優點和側重點:
深入理解Android卷I/卷II:基本就完成針對native層binder機制進行講述,並對binder驅動也進行了一定的分析,但是在demo上做的就比較簡略,只是帶過;
深入理解Android內核設計思想:講的太詳細,從智能指針開始,然後從java層的binder開始分析,再到native,直至binder驅動,涉及的面太廣了,如果對binder理解不深的可能會看得雲裏霧裏,喪失信心;
而網上有一些博客講的就不錯,先以demo爲例,讓讀者對binder有一個大概的印象,然後嘗試去分析,讓人對binder有一定的瞭解,貼出一下binder的參考書籍:
《深入理解Android卷I/卷II》
《深入理解Android內核設計思想》
博文:
android binder 基礎實例及解析
聽說你Binder機制學的不錯
二、 binder通信的最簡demo:
binder是基於client-service的通信方式,自然要從兩方面來定義,這裏我們寫一個native的service,名叫“MyService”,demo的目錄結構如下:
myservice/
├── Android.mk
├── client
│ ├── Android.mk
│ ├── main_client.cpp
│ └── Main_MyClient.cpp
├── interface
│ ├── IMyService.cpp
│ └── IMyService.h
└── service
├── Android.mk
├── Main_MyService.cpp
├── MyService.cpp
└── MyService.h
1. 定義IMyService.h
#ifndef IMyService_H
#define IMyService_H
#include <binder/IInterface.h>
namespace android {
/* 固定寫法:此處必須繼承自IInterface類 */
class IMyService : public IInterface
{
public:
DECLARE_META_INTERFACE(MyService);
virtual int setNum(int a) = 0;
virtual int getNum() = 0;
};
/* 對client端而言,只會暴露BnxxxService, 而不知道有BpxxxService
* 此類也是固定寫法 */
class BnMyService : public BnInterface<IMyService>
{
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
}
#endif
解釋一下Bn和Bp:
Bp:binder proxy,即代理的意思,程序實際運行過程中,client端並不知道有一個Bp的存在,但實際上,所有對binder的操作,都是BpxxxService這個代理來完成的;
Bn:binder native,爲service端服務的,用於從binder驅動中獲取client端傳過來的指令和數據,並調用xxxService裏面對client定義的這些接口的實現函數,怎麼實現的Bn不管,它只負責調用這些函數,並將結果寫入到binder驅動中,其實,可以把Bn理解爲服務端與binder驅動通信的管道;
2.定義IMyService.cpp:
#include "IMyService.h"
#include <binder/Parcel.h>
#include <utils/Log.h>
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: IMyService"
namespace android {
enum
{
SET_NUM = IBinder::FIRST_CALL_TRANSACTION,
GET_NUM,
};
/* binder代理端 */
class BpMyService : public BpInterface<IMyService> {
public:
BpMyService(const sp<IBinder>& impl)
: BpInterface<IMyService>(impl)
{
}
virtual int setNum(int a) {
ALOGD(" BpMyService::setNum a = %d ", a);
Parcel data,reply;
data.writeInt32(a);
remote()->transact(SET_NUM,data,&reply);
return reply.readInt32();
}
virtual int getNum() {
ALOGD(" BpMyService::getNum");
Parcel data,reply;
data.writeInterfaceToken(IMyService::getInterfaceDescriptor());
remote()->transact(GET_NUM,data,&reply);
return reply.readInt32();
}
};
/* 接口:這裏面會去new BpxxxSerivce*/
IMPLEMENT_META_INTERFACE(MyService, "jztech.binder.IMyService");
/* naive端代碼,即服務端代碼 ,此代碼的作用是從binder driver中
* 拿到client端的請求數據,如果有需要設入的值,就從binder driver
* 中讀取出來,然後執行服務器端的函數,將返回值寫入binder driver
* 實際上,Bn的代碼就是爲binder driver和service服務的通道 */
status_t BnMyService::onTransact (uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
int ret = -1;
switch (code) {
case SET_NUM:
{
int num = -1;
ALOGD("BnMyService::onTransact SET_NUM ");
num = data.readInt32();
ret = setNum(num);
reply->writeInt32(ret);
return NO_ERROR;
}
case GET_NUM:
{
ALOGD("BnMyService::onTransact GET_NUM ");
ret = getNum();
reply->writeInt32(ret);
return NO_ERROR;
}
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
}
client端調用的函數會通過transact將數據打包成Parcel 格式,然後發送到binder driver中;
BnMyService則是從binder drive中獲知了client端發送的消息,調用本地MyService中實現的函數,並將結果寫入binder驅動返回給client端;
3. 定義MyService.h:
#ifndef MyService_H
#define MyService_H
#include "../interface/IMyService.h"
#include <binder/BinderService.h>
namespace android {
class MyService : public BnMyService
{
public:
MyService();
~MyService();
/* 服務的唯一標識符,client端通過這個字符串找到此服務 */
static const char* getServiceName()
{
return "MyService";
}
virtual int setNum(int a);
virtual int getNum();
protected:
int myParam;
};
}
#endif
定義這個頭文件開始可以理解爲我們已經在寫service端了,這個頭文件就是爲了最終的MyServices函數實現,自然要繼承自BnMyService,這裏有一個函數叫getServiceName,裏面返回的是字符串“MyService”,這個就是我們native層服務的唯一標識,非常重要,後續client端在獲取service服務的時候,輸入這個就能找到我們的這個服務了;
- 定義MyService.cpp
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <binder/Parcel.h>
#include <utils/threads.h>
#include <cutils/properties.h>
#include "MyService.h"
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: MyService"
namespace android {
MyService::MyService() {
myParam = 0;
}
MyService::~MyService()
{
}
int MyService::setNum(int a) {
ALOGD("MyService::setNum a = %d myParam %d", a, myParam);
myParam += a;
return 0;//OK
}
int MyService::getNum() {
ALOGD("MyService::getNum myParam = %d", myParam);
return myParam;
}
}
這個文件最簡單了,因爲最終client端就是調用這個文件裏面的實現,在這裏,我們維護一個成員變量myParam,client端每調用一次setNum,我們就將myParam與設入值累加,然後client端再調用一次getNum,獲取返回的myParam;
好了,我想說,整個binder的核心其實已經寫完了,就這四個文件,讓他們分別作用於client端和service端就能夠實現binder的通信了。
接下來,就是寫測試代碼了,既然binder是Android中最著名的IPC通信,自然,我們要在兩個進程中去實現通信,我們寫兩個文件,一個作爲client端,一個作爲service端;
5. 編寫Main_MyService.cpp:
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include "MyService.h"
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: MyService-main"
using namespace android;
int main(int argc, char** argv)
{
/* 1. 打開binder driver並且進行內核地址的mmap */
sp<ProcessState> proc(ProcessState::self());
/* 2.獲取Android的SM並將我們的服務添加進去 */
sp<IServiceManager> sm = defaultServiceManager();
sm->addService(String16(MyService::getServiceName()), new MyService());
/* 3.啓動子線程,一般的server都不需要再啓動這樣一個子線程來協助主線程工作 */
ProcessState::self()->startThreadPool();
/* 4.loop循環,從binder driver中獲取指令,並由Bn的transact轉爲onTransact
* 最終回調到Bn端,進而執行xxxService裏面的代碼 */
IPCThreadState::self()->joinThreadPool();
return 0;
}
這個是服務端,代碼非常簡單,就是一個main函數,而且,全部都是固定句式,第二步中的getServiceName就是之前我說的唯一標識符,當然,你完全可以不用在IMyService.h去實現這個函數,直接在這裏傳入“MyService”即可,但是,我還是要強調的是,這個標識符很重要,下面client端馬上就能用到,不要寫錯了哦~
第三步創建一個子線程,這步可以直接刪掉,我們看安卓源碼mediaserver是有這句的,大神們分析是因爲當一個進程註冊了多個server時,可能導致主線程太過繁忙,所以創建這麼個子線程協同工作,當然,我們自己的server完全沒那麼複雜,所以可以給直接幹掉;
6. 編寫Main_MyClient.cpp:
#include <stdio.h>
#include "../interface/IMyService.h"
#include <binder/IServiceManager.h>
#define LOG_NDEBUG 0
#define LOG_TAG "JzTech: Client-main"
using namespace android;
sp<IMyService> mMyService;
void initMyServiceClient() {
if (mMyService == 0) {
/* 1.獲取SM,並創建client端的gProcess,這裏面又會去打開binder driver */
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
/* 2.由名字獲取服務 */
binder = sm->getService(String16("MyService"));
if (binder != 0)
{
break;
}
ALOGE("MyService not published, waiting...");
sleep(1);
} while (1);
mMyService = interface_cast<IMyService>(binder);
}
}
int main(int argc, char* argv[]) {
/* 獲取binder service */
initMyServiceClient();
if(mMyService ==NULL) {
ALOGE("cannot find MyService");
return -1;
}
while(1) {
mMyService->setNum(1);
sleep(1);
ALOGD("getNum %d", mMyService->getNum());
}
return 0;
}
這裏,我們定義了一個全局變量mMyService來獲取服務,直接看主函數,先調用initMyServiceClient去獲取service端的服務,然後使用一個while循環不停地往service端請求setNum,之後再請求getNum看是否實現了和service端的通信,整個demo就真的全部寫完了;
7. 三個Android.mk:
a. 最外層Android.mk:
include $(call all-subdir-makefiles)
b. client端mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Main_MyClient.cpp \
../interface/IMyService.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder
LOCAL_C_INCLUDES := \
frameworks/base/include \
frameworks/native/include \
$(VENDOR_SDK_INCLUDES)
LOCAL_MODULE:= my_binder_client
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
c. service端mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Main_MyService.cpp \
MyService.cpp \
../interface/IMyService.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder
LOCAL_C_INCLUDES := \
frameworks/base/include \
frameworks/native/include/binder \
$(VENDOR_SDK_INCLUDES) \
LOCAL_MODULE:= my_binder_service
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
三、 驗證結果:
將生成的兩個可執行文件推入單板,然後將service端作爲後臺進程執行,再執行client端:
通過adb獲取打印log:
源碼位置:
Git上源碼路徑