最近在項目中遇到一個問題, 要對某個節(jié)點(dev/xxx)進(jìn)行寫操作, 但這個設(shè)備節(jié)點只允許root用戶才能進(jìn)行寫操作, 因此不能通過Java或者JNI方式直接去訪問, 因此想到了兩種方法:
- 通過在init.rc中監(jiān)聽一個系統(tǒng)屬性的值, 當(dāng)屬性變?yōu)槟硞€值時, 觸發(fā)一個可執(zhí)行文件進(jìn)行讀寫
- 編寫一個Native Service, 然后以root的身份運行, 通過跨進(jìn)程調(diào)用, 在Service中進(jìn)行寫操作
最后通過第一種方式解決了問題, 原因是寫的頻率很低, 基本一個手機(jī)就一次, 所以沒必要弄成服務(wù), 但本著學(xué)習(xí)的態(tài)度, 當(dāng)然要了解下第二種方式的實現(xiàn)方法, 因此就有了這篇文章, 廢話就到這, 開始正文.
定義Binder接口
要實現(xiàn)跨進(jìn)程, 自然是使用Binder了, 因此我們首先要定義一個用于跨進(jìn)程的接口, 我們通過一個讀取和設(shè)置藍(lán)牙地址的例子為例, 來講解具體實現(xiàn)方法, 接口名為IDeviceMac
, 代碼如下:
IDeviceMac.h
#ifndef XTC_IDEVICEMAC_H
#define XTC_IDEVICEMAC_H
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <utils/String8.h>
#include <android/log.h>
#ifdef TAG
#undef TAG
#endif
#define TAG "DeviceMac"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
namespace android {
class IDeviceMac : public IInterface {
public:
enum {
SET_BT_MAC = IBinder::FIRST_CALL_TRANSACTION,
GET_BT_MAC,
};
virtual int setBTMac(String8 bt) = 0;
virtual String8 getBTMac() = 0;
DECLARE_META_INTERFACE(DeviceMac);
};
//-------------------------------------------
class BnDeviceMac : public BnInterface<IDeviceMac> {
public:
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags);
};
} // end namespace android
#endif
代碼很簡單, 定義一個類繼承自IInterface
, 里面接口就是我們自己要用到的, 其中DECLARE_META_INTERFACE(DeviceMac);
是一個宏定義, 用來定義繼承IInterface
必須實現(xiàn)的兩個方法, 具體是什么方法后面接口實現(xiàn)部分講.
可以看到我們定義IDeviceMac
后, 還定義了一個類BnDeviceMac
,這個是Binder調(diào)用的一個規(guī)范, 即定義Ixxx
接口后, Bpxxx
表示Client端接口, Bnxxx
表示Service端接口, Bpxxx
和Bnxxx
都需要我們?nèi)崿F(xiàn)具體內(nèi)容, 并且Bnxxx
和Bpxxx
中的方法和Ixxx
中的方法是一一對應(yīng)的.
實現(xiàn)BpDeviceMac和BnDeviceMac::onTransact()
IDeviceMac.cpp
#include "IDeviceMac.h"
namespace android {
class BpDeviceMac : public BpInterface<IDeviceMac> {
public:
BpDeviceMac(const sp<IBinder>& impl) : BpInterface<IDeviceMac>(impl)
{
}
int setBTMac(String8 bt) {
LOGI("Bp setBT");
Parcel data, reply;
data.writeInterfaceToken(IDeviceMac::getInterfaceDescriptor());
data.writeString8(bt);
remote()->transact(SET_BT_MAC, data, &reply);
return reply.readInt32();
}
String8 getBTMac() {
LOGI("Bp getBT");
Parcel data, reply;
data.writeInterfaceToken(IDeviceMac::getInterfaceDescriptor());
remote()->transact(GET_BT_MAC, data, &reply);
return reply.readString8();
}
};
IMPLEMENT_META_INTERFACE(DeviceMac, "DeviceMac");
/* Macro above expands to code below.
const android::String16 IDeviceMac::descriptor("DeviceMac");
const android::String16& IDeviceMac::getInterfaceDescriptor() const {
return IDeviceMac::descriptor;
}
android::sp<IDeviceMac> IDeviceMac::asInterface(const android::sp<android::IBinder>& obj) {
android::sp<IDeviceMac> intr;
if (obj != NULL) {
intr = static_cast<IDeviceMac*>(obj->queryLocalInterface(IDeviceMac::descriptor).get());
if (intr == NULL) {
intr = new BpDeviceMac(obj);
}
}
return intr;
}
*/
//---------------------------------------------------
status_t BnDeviceMac::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
CHECK_INTERFACE(IDeviceMac, data, reply);
LOGI("Bn onTransact code:%d", code);
switch(code) {
case SET_BT_MAC:
reply->writeInt32(setBTMac(data.readString8()));
return NO_ERROR;
case GET_BT_MAC:
reply->writeString8(getBTMac());
return NO_ERROR;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
} // end namespace android
上面代碼中IMPLEMENT_META_INTERFACE(DeviceMac, "DeviceMac");
下面注釋掉的內(nèi)容就是這個宏定義代表的實際代碼, 也是就是說IDeviceMac.h
中的那個宏定義其實就是定義這兩個方法.
BpDeviceMac
里面的內(nèi)容就是把相關(guān)參數(shù)寫到Parcel
中, 這是一個用來讀寫跨進(jìn)程參數(shù)的類, 然后調(diào)用remote()->transact()
, 就調(diào)用到BnDeviceMac::onTransact()
中,BnDeviceMac::onTransact()
函數(shù)中已經(jīng)跨過進(jìn)程了, 具體怎么做到的, 這就涉及到IPC原理了,這里不做討論, onTransact
做的事情也很簡單, 就是從Parcel
中將Client傳過來的數(shù)據(jù)讀出來, 然后調(diào)用BnDeviceMac
中對應(yīng)的實現(xiàn)方法, 這里需要注意, 由于BnDeviceMac::onTransact()
代碼和BpDeviceMac
寫在了同一個文件中, 看起來有點像BnDeviceMac
調(diào)用BpDeviceMac
的 一樣, 其實是BnDeviceMac::onTranscat()
中調(diào)用的setBTMac() getBTMac()
是在調(diào)用BnDeviceMac
中實現(xiàn)的方法, 接下來就講BnDeviceMac
的實現(xiàn).
BnDeviceMac實現(xiàn)(Service)
DeviceMacService.h
#ifndef XTC_DEVICEMACSERVICE_H
#define XTC_DEVICEMACSERVICE_H
#include "IDeviceMac.h"
#define SERVER_NAME "DeviceMacService"
namespace android {
class DeviceMacService : public BnDeviceMac {
public:
DeviceMacService();
virtual ~DeviceMacService();
//IDeviceMac
virtual int setBTMac(String8 bt);
virtual String8 getBTMac();
};
} // end namespace android
#endif
DeviceMacService.cpp
#include "DeviceMacService.h"
namespace android {
DeviceMacService::DeviceMacService() {
}
DeviceMacService::~DeviceMacService() {
}
int DeviceMacService::setBTMac(String8 bt) {
LOGI("Bn setBT, bt:%s", bt.string());
return NO_ERROR;
}
String8 DeviceMacService::getBTMac() {
LOGI("Bn getBT");
return String8("4a:4b:4c:3a:3b:3c");
}
} // end namespace android
DeviceMacService
這個類繼承了BnDeviceMac
, 實現(xiàn)了其中的方法, 所以BnDeviceMac::onTransact()
方法中相關(guān)調(diào)用就會調(diào)到DeviceMacService
, 在DeviceMacService
中, 我們就能做我們實際想做的事情了.
啟動Service和Client端調(diào)用
main_server.cpp
#include "DeviceMacService.h"
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
using namespace android;
sp<IDeviceMac> getService() {
sp<IServiceManager> sm = defaultServiceManager();
if (sm == NULL) {
LOGE("can not get service manager");
}
sp<IBinder> binder = sm->getService(String16(SERVER_NAME));
if (binder == NULL) {
LOGE("can not get service");
}
sp<IDeviceMac> service = interface_cast<IDeviceMac>(binder);
if (service == NULL) {
LOGE("can not cast interface");
}
return service;
}
int main(int argc, char** argv) {
if (argc == 1) {
LOGI("start DeviceMacService");
defaultServiceManager()->addService(String16(SERVER_NAME), new DeviceMacService());
android::ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
} else if (argc == 2) {
sp<IDeviceMac> devMacServer = getService();
devMacServer->setBTMac(String8("1a:1b:1c:1a:1b:1c"));
String8 bt = devMacServer->getBTMac();
LOGI("get bt mac:%s", bt.string());
}
return 0;
}
添加服務(wù)的代碼很簡單, 三行代碼, 固定的操作, 獲取服務(wù)過程用, 有個interfa_cast
的函數(shù), 會將IBinder
作為參數(shù) new
一個BpDeviceMac
對象, 我們通過這個對象進(jìn)行相關(guān)接口調(diào)用, 最終調(diào)用到DeviceMacService
.
注: 為了測試方便, 我將添加Service和調(diào)用Service寫在了同一個可執(zhí)行文件中, 實際項目都是分開的.
編譯
現(xiàn)在萬事具備, 只等編譯運行了, Android.mk
代碼如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := macserver
LOCAL_MODULE_TAGS := optional
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include \
frameworks/native/include \
system/core/include
LOCAL_SRC_FILES := IDeviceMac.cpp DeviceMacService.cpp main_server.cpp
LOCAL_SHARED_LIBRARIES := libutils libcutils libbinder libhardware
include $(BUILD_EXECUTABLE)
整個代碼目錄結(jié)構(gòu)如下:
編譯方法:
- 確保當(dāng)前Android源碼全部編譯通過(有些依賴需先編譯好)
- 將service目錄放到Android源碼目錄中(比如vendor/qcom/service)
- 在Android源碼根目錄執(zhí)行
mmm vendor/qcom/service
- 執(zhí)行完后編譯的可執(zhí)行文件在
out/target/product/xxx/system/bin/
下面(xxx為lunch的product) - 將編譯好的可執(zhí)行文件
macserver
通過adb push 到手機(jī)system/bin/下面(adb需要root, 即執(zhí)行 adb root , adb remount) - 執(zhí)行
adb shell chmod 777 /system/bin/macserver
加上可執(zhí)行權(quán)限, 然后啟動服務(wù), 執(zhí)行adb shell /system/bin/macserver
(會阻塞當(dāng)前窗口) - 重新開一個窗口執(zhí)行adb命令
adb shell /system/bin/macserver 1
即可調(diào)用Service, 可以通過logcat過濾``DeviceMac```來查看log.
如果你想在開機(jī)后就自動啟動服務(wù), 并且指定Service所屬的用戶組, 可在init.rc中加入如下代碼
service macserver /system/bin/macserver
class main
user root
group root
注: 如果要把這個可執(zhí)行文件編譯到系統(tǒng)中,還需在相關(guān)的product的配置mk中添加PRODUCT_PACKAGES += macserver
另一種寫法
上述流程是一個完整的Native Service實現(xiàn)過程, 以及調(diào)用方式, 其實還有一種簡潔的方式, 就是寫一個類繼承自BBinder
, 然后實現(xiàn)onTransact()
方法, 定義如下:
class NativeService : public BBinder
{
public:
NativeService();
virtual ~NativeService();
virtual status_t onTransact(uint32_t, const Parcel&, Parcel*, uint32_t);
};
這樣就不用管Bn和Bp端了, 相當(dāng)于只用實現(xiàn)Service端, 但在Client端調(diào)用的時候, 通過sp<IBinder> binder = sm->getService(String16(SERVER_NAME));
獲取引用后, 就不用轉(zhuǎn)為相關(guān)定義的接口了, 因為你根本沒定義接口, 這時候調(diào)用只能調(diào)用其transact()
方法, 通過第一個參數(shù)區(qū)分是那種情況的調(diào)用, 參數(shù)傳遞也是通過寫到Parcel
中Service端去讀, 本質(zhì)上和上面BnBp架構(gòu)一樣, 只是可以少寫點代碼, 但缺點也很明顯, 作為功能接口這樣寫肯定不好, 調(diào)用者使用起來很不方便.
個人理解, 這種方法和上述講的方法區(qū)別只是一個封裝的問題, Bn Bp方式只是對接口寫法的一個規(guī)范, 讓接口使用者調(diào)用起來更加清晰明了.
Java端調(diào)用
其實我們雖然是使用C++寫的Native Service, 但Android系統(tǒng)為我們做了很多事, 我們其實也可以通過Java直接調(diào)用的, 方法如下:
public void testNativeService() {
IBinder service = ServiceManager.getService("DeviceMacService");
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
boolean res = service.transact(2, data, reply, 0);
if (!res) {
Log.e("Test", "transact fail");
}
String result = reply.readString();
data.recycle();
reply.recycle();
} catch (RemoteException e) {
e.printStackTrace();
}
}
但由于ServiceManager
這個類不是公開的, 你只能通過反射去調(diào)用, 或者是Android系統(tǒng)開發(fā)在源碼中進(jìn)行編譯和調(diào)用, 另外Java使用了String
作為參數(shù)的話, C++就要使用String16
這個來與其對應(yīng).
編寫AIDL
如果你既想少寫點代碼, 又想調(diào)用起來比較方便, 這個也有實現(xiàn)方法, 就是編寫AIDL文件, 和Java里面的AIDL類似, 只不過你要放在Android源碼里面進(jìn)行編譯, 系統(tǒng)會自動根據(jù)Ixxx.aidl
在編譯過程中生成Ixxx.cpp
, 這個cpp文件中就和上面我們寫的IDeviceMac.cpp
內(nèi)容基本一致, 也就是說這部分代碼可以自動生成了, 然后你只需要在Service端寫一個類繼承Bnxxx
然后實現(xiàn)AIDL文件中定義的方法即可, 使用非常方便, Android 7.1上面的ICameraService.aidl
就是以這種方式實現(xiàn)的, 部分代碼如下, 可以參考一下:
frameworks/av/camera/aidl/android/hardware/ICameraService.aidl
/**
* Types for getNumberOfCameras
*/
const int CAMERA_TYPE_BACKWARD_COMPATIBLE = 0;
const int CAMERA_TYPE_ALL = 1;
/**
* Return the number of camera devices available in the system
*/
int getNumberOfCameras(int type);
/**
* Fetch basic camera information for a camera device
*/
CameraInfo getCameraInfo(int cameraId);
/**
* Default UID/PID values for non-privileged callers of
* connect(), connectDevice(), and connectLegacy()
*/
const int USE_CALLING_UID = -1;
const int USE_CALLING_PID = -1;
/**
* Open a camera device through the old camera API
*/
ICamera connect(ICameraClient client,
int cameraId,
String opPackageName,
int clientUid, int clientPid);
如果以這種方式實現(xiàn)的話, 編譯的Android.mk中需要加入如下代碼:
LOCAL_AIDL_INCLUDES := \
frameworks/av/camera/aidl \
LOCAL_SRC_FILES := \
aidl/android/hardware/ICameraService.aidl \
即要引入頭文件路徑和aidl源文件.
如果你想要看下自動生成的Ixxx.cpp
的代碼, 其路徑為:
out/target/product/xxx1/obj/xxx2/xxx3_intermediates/aidl-generated/
xxx1表示你lunch時選的product, xxx2表示你編譯的模塊類型, 通常是 SHARED_LIBRARIES 或者
EXECUTABLES, xxx3表示你編譯的模塊中LOCAL_MODULE定義的名字.
例如: out/target/product/msm8953/obj/SHARED_LIBRARIES/libcamera_client_intermediates/aidl-generated/src/aidl/android/hardware/ICameraService.cpp