通过C++实现Android Native Service

最近在项目中遇到一个问题, 要对某个节点(dev/xxx)进行写操作, 但这个设备节点只允许root用户才能进行写操作, 因此不能通过Java或者JNI方式直接去访问, 因此想到了两种方法:

  1. 通过在init.rc中监听一个系统属性的值, 当属性变为某个值时, 触发一个可执行文件进行读写
  2. 编写一个Native Service, 然后以root的身份运行, 通过跨进程调用, 在Service中进行写操作

最后通过第一种方式解决了问题, 原因是写的频率很低, 基本一个手机就一次, 所以没必要弄成服务, 但本着学习的态度, 当然要了解下第二种方式的实现方法, 因此就有了这篇文章, 废话就到这, 开始正文.

定义Binder接口

要实现跨进程, 自然是使用Binder了, 因此我们首先要定义一个用于跨进程的接口, 我们通过一个读取和设置蓝牙地址的例子为例, 来讲解具体实现方法, 接口名为IDeviceMac, 代码如下:
IDeviceMac.h

 

#ifndef XTC_IDEVICEMAC_H
#define XTC_IDEVICEMAC_H

#include 
#include 
#include 
#include 
#include 

#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 {
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必须实现的两个方法, 具体是什么方法后面接口实现部分讲.
可以看到我们定义IDeviceMac后, 还定义了一个类BnDeviceMac,这个是Binder调用的一个规范, 即定义Ixxx接口后, Bpxxx表示Client端接口, Bnxxx表示Service端接口, BpxxxBnxxx都需要我们去实现具体内容, 并且BnxxxBpxxx中的方法和Ixxx中的方法是一一对应的.

实现BpDeviceMac和BnDeviceMac::onTransact()

IDeviceMac.cpp

 

#include "IDeviceMac.h"

namespace android {

class BpDeviceMac : public BpInterface {

public:
    BpDeviceMac(const sp& impl) : BpInterface(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::asInterface(const android::sp& obj) {
    android::sp intr;
    if (obj != NULL) {
        intr = static_cast(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");下面注释掉的内容就是这个宏定义代表的实际代码, 也是就是说IDeviceMac.h中的那个宏定义其实就是定义这两个方法.
BpDeviceMac 里面的内容就是把相关参数写到Parcel中, 这是一个用来读写跨进程参数的类, 然后调用remote()->transact(), 就调用到BnDeviceMac::onTransact()中,BnDeviceMac::onTransact()函数中已经跨过进程了, 具体怎么做到的, 这就涉及到IPC原理了,这里不做讨论, onTransact做的事情也很简单, 就是从Parcel中将Client传过来的数据读出来, 然后调用BnDeviceMac中对应的实现方法, 这里需要注意, 由于BnDeviceMac::onTransact()代码和BpDeviceMac写在了同一个文件中, 看起来有点像BnDeviceMac调用BpDeviceMac的 一样, 其实是BnDeviceMac::onTranscat()中调用的setBTMac() getBTMac()是在调用BnDeviceMac中实现的方法, 接下来就讲BnDeviceMac的实现.

BnDeviceMac实现(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, 实现了其中的方法, 所以BnDeviceMac::onTransact()方法中相关调用就会调到DeviceMacService, 在DeviceMacService中, 我们就能做我们实际想做的事情了.

启动Service和Client端调用

main_server.cpp

 

#include "DeviceMacService.h"
#include 
#include 
#include 

using namespace android;

sp getService() {
    sp sm = defaultServiceManager();
    if (sm == NULL) {
        LOGE("can not get service manager");
    }
    sp binder = sm->getService(String16(SERVER_NAME));
    if (binder == NULL) {
        LOGE("can not get service");
    }
    sp service = interface_cast(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 devMacServer = getService();

        devMacServer->setBTMac(String8("1a:1b:1c:1a:1b:1c"));
        String8 bt = devMacServer->getBTMac();
        LOGI("get bt mac:%s", bt.string());
    }
    return 0;
}

添加服务的代码很简单, 三行代码, 固定的操作, 获取服务过程用, 有个interfa_cast的函数, 会将IBinder作为参数 new 一个BpDeviceMac对象, 我们通过这个对象进行相关接口调用, 最终调用到DeviceMacService.
注: 为了测试方便, 我将添加Service和调用Service写在了同一个可执行文件中, 实际项目都是分开的.

编译

现在万事具备, 只等编译运行了, 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)

整个代码目录结构如下:

通过C++实现Android Native Service_第1张图片

目录结构.JPG

编译方法:

  1. 确保当前Android源码全部编译通过(有些依赖需先编译好)
  2. 将service目录放到Android源码目录中(比如vendor/qcom/service)
  3. 在Android源码根目录执行 mmm vendor/qcom/service
  4. 执行完后编译的可执行文件在out/target/product/xxx/system/bin/下面(xxx为lunch的product)
  5. 将编译好的可执行文件macserver通过adb push 到手机system/bin/下面(adb需要root, 即执行 adb root , adb remount)
  6. 执行adb shell chmod 777 /system/bin/macserver加上可执行权限, 然后启动服务, 执行adb shell /system/bin/macserver(会阻塞当前窗口)
  7. 重新开一个窗口执行adb命令adb shell /system/bin/macserver 1即可调用Service, 可以通过logcat过滤``DeviceMac```来查看log.

如果你想在开机后就自动启动服务, 并且指定Service所属的用户组, 可在init.rc中加入如下代码

 

service macserver /system/bin/macserver 
     class main
     user root
     group root

注: 如果要把这个可执行文件编译到系统中,还需在相关的product的配置mk中添加PRODUCT_PACKAGES += macserver

另一种写法

上述流程是一个完整的Native Service实现过程, 以及调用方式, 其实还有一种简洁的方式, 就是写一个类继承自BBinder, 然后实现onTransact()方法, 定义如下:

 

    class NativeService : public BBinder  
    {  
    public:  
        NativeService();  
        virtual ~NativeService();  
        virtual status_t onTransact(uint32_t, const Parcel&, Parcel*, uint32_t);  
    };  

这样就不用管Bn和Bp端了, 相当于只用实现Service端, 但在Client端调用的时候, 通过sp binder = sm->getService(String16(SERVER_NAME));获取引用后, 就不用转为相关定义的接口了, 因为你根本没定义接口, 这时候调用只能调用其transact()方法, 通过第一个参数区分是那种情况的调用, 参数传递也是通过写到Parcel中Service端去读, 本质上和上面BnBp架构一样, 只是可以少写点代码, 但缺点也很明显, 作为功能接口这样写肯定不好, 调用者使用起来很不方便.
个人理解, 这种方法和上述讲的方法区别只是一个封装的问题, Bn Bp方式只是对接口写法的一个规范, 让接口使用者调用起来更加清晰明了.

Java端调用

其实我们虽然是使用C++写的Native Service, 但Android系统为我们做了很多事, 我们其实也可以通过Java直接调用的, 方法如下:

 

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这个类不是公开的, 你只能通过反射去调用, 或者是Android系统开发在源码中进行编译和调用, 另外Java使用了String作为参数的话, C++就要使用String16这个来与其对应.

编写AIDL

如果你既想少写点代码, 又想调用起来比较方便, 这个也有实现方法, 就是编写AIDL文件, 和Java里面的AIDL类似, 只不过你要放在Android源码里面进行编译, 系统会自动根据Ixxx.aidl在编译过程中生成Ixxx.cpp, 这个cpp文件中就和上面我们写的IDeviceMac.cpp内容基本一致, 也就是说这部分代码可以自动生成了, 然后你只需要在Service端写一个类继承Bnxxx然后实现AIDL文件中定义的方法即可, 使用非常方便, Android 7.1上面的ICameraService.aidl就是以这种方式实现的, 部分代码如下, 可以参考一下:
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);

如果以这种方式实现的话, 编译的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

 

你可能感兴趣的:(android,java)