欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

通过C++实现Android Native Service

程序员文章站 2024-03-23 23:28:40
...

最近在项目中遇到一个问题, 要对某个节点(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 <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必须实现的两个方法, 具体是什么方法后面接口实现部分讲.
可以看到我们定义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<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");下面注释掉的内容就是这个宏定义代表的实际代码, 也是就是说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 <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;
}

添加服务的代码很简单, 三行代码, 固定的操作, 获取服务过程用, 有个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

目录结构.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<IBinder> 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