Android native service 示例

安卓系统上添加系统服务,可以通过 android.os.ServiceManager 来添加使用 java 实现的系统服务,放在 SytemServer 进程(内置在源码编译)或者放在 app 进程(需要是系统签名的系统应用)。
由于运行在 SystemServer 进程或者运行在app进程,某些特殊操作就面临权限不足的问题,比如操作kernel的节点,这个时候就需要使用使用 root 权限运行的进程了。(当然严格来说,通过java实现的服务,同样可以以 root 权限运行,比如将相应的代码编译成 jar,开机之后通过 appprocess 运行)。

native service 需要定义一个接口类,下面的示例作为参考

// INativeServiceSample.h
#ifndef _I_NATIVES_ERVICE_SAMPLE_H_
#define _I_NATIVES_ERVICE_SAMPLE_H_

#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <binder/PermissionController.h>
#include <binder/ProcessInfoService.h>
#include <binder/IResultReceiver.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <android/log.h>

#ifdef TAG
#undef TAG
#endif
#define TAG "NativeService"
#define NATIVESERVICE_NAME "nativeservice"

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,TAG ,__VA_ARGS__)
#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 {

    enum STATUS {
        SUCCESS = NO_ERROR,
        FAILED = UNKNOWN_TRANSACTION
    };
    class INativeServiceSample : public IInterface {
        public:
        virtual int invoke(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) = 0;
        DECLARE_META_INTERFACE(NativeServiceSample);
    };

    class BnNativeServiceSample : public BnInterface<INativeServiceSample>
    {
    public:
        status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags);
        status_t dump(int fd, const Vector<String16> &args);
        int onShellCommand(int in, int out, int err, const Vector<String16> &args);
        virtual int invoke(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) = 0;
    private:
        int shellCommand(int in, int out, int err, const Vector<String16> &args);
        
    };
}

#endif

这里的主要工作是定义一个 server 端和 client 端都需要实现的函数, invoke ,以及 DECLARE_META_INTERFACE 这一行,DECLARE_META_INTERFACE 是一个宏定义,后面的小括号里面的 NativeServiceSample 则是当前类 INativeServiceSample 中的 NativeServiceSample 。之类会为这个类默认声明好相应的一个service需要是实现的函数。这里有严格的命名规范。

然后实现客户端和服务端,也即一个 Bp(客户端) 一个 Bn(服务端)
下面是相应的定义

//INativeServiceSample.cpp
#include "INativeServiceSample.h"
namespace android
{
        class BpNativeServiceSample : public BpInterface<INativeServiceSample>
    {
    public:
        BpNativeServiceSample(const sp<IBinder> &impl) : BpInterface<INativeServiceSample>(impl) {}
        int invoke(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
        {
            Parcel send;
            send.writeInterfaceToken(getInterfaceDescriptor());
            send.appendFrom(&data, 0, data.dataSize());
            return remote()->transact(code, send, reply);
        }
    };

    IMPLEMENT_META_INTERFACE(NativeServiceSample, NATIVESERVICE_NAME);

    status_t BnNativeServiceSample::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
    {
        if(code == SHELL_COMMAND_TRANSACTION)
        {
            int in = data.readFileDescriptor();
            int out = data.readFileDescriptor();
            int err = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++)
            {
                args.add(data.readString16());
            }
            status_t result = 0;
            sp<IBinder> unusedCallback;
            sp<IResultReceiver> resultReceiver;
            if ((result = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR)
            {
                return result;
            }
            if ((result = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR)
            {
                return result;
            }
            result = shellCommand(in, out, err, args);
            if (resultReceiver != nullptr)
            {
                resultReceiver->send(result);
            }
            return result;
        }
        else if(IBinder::FIRST_CALL_TRANSACTION <= code && code <= IBinder::LAST_CALL_TRANSACTION)
        {
            CHECK_INTERFACE(INativeServiceSample, data, reply);
            return invoke(code, data, reply, flags);
        }
        else
        {
            return BBinder::onTransact(code, data, reply, flags);
        }
    }

    status_t BnNativeServiceSample::shellCommand(int in, int out, int err, const Vector<String16> &args)
    {
        return onShellCommand(in, out, err, args);
    }

    status_t BnNativeServiceSample::onShellCommand(int in, int out, int err, const Vector<String16> &args)
    {
        LOGI("onShellCommand not implementation yet");
        write(out, "onShellCommand not implementation yet\n", 38);
        size_t size = args.size();
        for (size_t i = 0; i < size; i++)
        {
            LOGI("Received No.%zu arg: %s", i, String8(args[i]).string());
        }
        return SUCCESS;
    }

    status_t BnNativeServiceSample::dump(int fd, const Vector<String16> &args)
    {
        LOGI("dump not implementation yet");
        write(fd, "dump not implementation yet\n", 28);
        size_t size = args.size();
        for (size_t i = 0; i < size; i++)
        {
            LOGI("Received No.%zu arg: %s", i, String8(args[i]).string());
        }
        return SUCCESS;
    }
}

BpNativeServiceSample : public BpInterface<INativeServiceSample> 是客户端的定义,它的构造函数是固定格式。
class BnNativeServiceSample : public BnInterface<INativeServiceSample> 则是服务端的定义,它里面有一个用于与其他进程通信最重要的函数onTransact
而 dump 函数则是我们使用 dumpsys servicename args...这样的命令行方式去获取服务的信息时会被调用的函数,我们可以在这和函数里面接收命令行参数,解析命令以及输出内容,其中 fd 命令行传入的一个文件描述符,通常它是 stdout ,后面的 args则是命令行参数,安卓的命令参数是通过 String16 格式传入。
对于客户端,我们对调用者提供的接口是 invoke(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) 。客户端也可以直接使用 service 的 tract 接口来对服务端进行调用,为了隐藏 getInterfaceDescriptor() ,我将这部分操作预先包含在 invoke 里面,调用者提供 code,data,reply以及 flags 即可(flags这里我实际上没有使用到)
这里由一个非常重要的 IMPLEMENT_META_INTERFACE ,这个宏会在这部分展开,将在接口类内声明的内容实现放在此处,同样的,这里也有严格的命名要求。
服务端的 onTransact实现如下

    status_t BnNativeServiceSample::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
    {
        if(code == SHELL_COMMAND_TRANSACTION)
        {
            int in = data.readFileDescriptor();
            int out = data.readFileDescriptor();
            int err = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++)
            {
                args.add(data.readString16());
            }
            status_t result = 0;
            sp<IBinder> unusedCallback;
            sp<IResultReceiver> resultReceiver;
            if ((result = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR)
            {
                return result;
            }
            if ((result = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR)
            {
                return result;
            }
            result = shellCommand(in, out, err, args);
            if (resultReceiver != nullptr)
            {
                resultReceiver->send(result);
            }
            return result;
        }
        else if(IBinder::FIRST_CALL_TRANSACTION <= code && code <= IBinder::LAST_CALL_TRANSACTION)
        {
            CHECK_INTERFACE(INativeServiceSample, data, reply);
            return invoke(code, data, reply, flags);
        }
        else
        {
            return BBinder::onTransact(code, data, reply, flags);
        }
    }

这里进来之后,我们受限检查 code 是否是 SHELL_COMMAND_TRANSACTION,这个 code 表示本次IPC调用是通过 cmd servicename args... 的方式调用的,这里把 stdin,stdout,stderr,args 封装好传给新增的 shellCommand ,shellCommand 里面调用 onShellCommand 。
然后判断是code是否是Binder为开发者预留的范围,如果是这个范围,就交给我们自己实现的 invoke 处理 ,首先是一个 CHECK_INTERFACE(INativeServiceSample, data, reply); 这里主要作用是检查调用 service 时,传递给服务端的 parcel 的 interface 是否正确。
如果code不是以上几种情况,那么就交给父类处理。可以查看 aosp/frameworks/native/libs/binder/Binder.cpp

// NOLINTNEXTLINE(google-default-arguments)
status_t BBinder::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/)
{
    switch (code) {
        case INTERFACE_TRANSACTION:
            reply->writeString16(getInterfaceDescriptor());
            return NO_ERROR;

        case DUMP_TRANSACTION: {
            int fd = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
               args.add(data.readString16());
            }
            return dump(fd, args);
        }

        case SHELL_COMMAND_TRANSACTION: {
            int in = data.readFileDescriptor();
            int out = data.readFileDescriptor();
            int err = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
               args.add(data.readString16());
            }
            sp<IShellCallback> shellCallback = IShellCallback::asInterface(
                    data.readStrongBinder());
            sp<IResultReceiver> resultReceiver = IResultReceiver::asInterface(
                    data.readStrongBinder());

            // XXX can't add virtuals until binaries are updated.
            //return shellCommand(in, out, err, args, resultReceiver);
            (void)in;
            (void)out;
            (void)err;

            if (resultReceiver != nullptr) {
                resultReceiver->send(INVALID_OPERATION);
            }

            return NO_ERROR;
        }

        case SYSPROPS_TRANSACTION: {
            report_sysprop_change();
            return NO_ERROR;
        }

        default:
            return UNKNOWN_TRANSACTION;
    }
}

在这里我们就知道 dump 其实也是一个特殊的 code 。

p.s: 这里我们当然可以在接口定义的时候就生命客户端和服务端都共有的相同实现的接口,可是这样做每次新增接口都需要在双方一起增加实现,我用一个 invoke 来中转调用。
p.s: code 是服务端用来区分客户端想调用哪个函数的。

下面来看看真正的服务端的声明和实现

// NativeServiceSampleService.h
#ifndef _NATIVE_SERVICE_SAMPLE_SERVICE_H_
#define _NATIVE_SERVICE_SAMPLE_SERVICE_H_
#include "INativeServiceSample.h"
#include <map>
namespace android
{
    class NativeServiceSampleService : public BnNativeServiceSample
    {
    public:
        typedef status_t (NativeServiceSampleService::*local_method)(const Parcel &data, Parcel *reply);
        NativeServiceSampleService(){loadMethods();};
        ~NativeServiceSampleService(){};
        status_t sampleCallFunction(const Parcel &data, Parcel *reply);
        status_t sampleGetFunction(const Parcel &data, Parcel *reply);
        status_t getMacByInterfaceName(const Parcel &data, Parcel *reply);
    private:
        int invoke(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags);
        int getCallingUid();
        int getCallingPid();
        void loadMethods();
        std::map<uint32_t,local_method> methods;
    };
};
#endif

这里定义了三个功能函数,sampleCallFunction,sampleGetFunction,getMacByInterfaceName,我将这几个函数的输入参数统一了,然后用一个 map 去保存 code 和指向这几个函数的函数指针。

//NativeServiceSampleService.cpp
#include "NativeServiceSampleService.h"
#include "Cmdid.h"
#include <errno.h>
#include <string.h>
#include <binder/IPCThreadState.h>
#include <hwbinder/IPCThreadState.h>

namespace android {
    void NativeServiceSampleService::loadMethods()
    {
        methods[SAMPLE_CALL] = &NativeServiceSampleService::sampleCallFunction;
        methods[SAMPLE_GET] = &NativeServiceSampleService::sampleGetFunction;
        methods[SAMPLE_GET_MAC] = &NativeServiceSampleService::getMacByInterfaceName;
    }
    int NativeServiceSampleService::invoke(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){
        std::map<uint32_t, local_method>::iterator iter = methods.find(code);
        LOGV("uid:%d pid:%d called method %u",getCallingUid(),getCallingPid(),code);
        if (iter != methods.end())
        {
            return (this->*(iter->second))(data, reply);
        }
        else
        {
            LOGE("can't find such a method whose cmdid is %u", code);
            return -1;
        }
    }
    
    status_t NativeServiceSampleService::sampleCallFunction(const Parcel&data, Parcel* reply){
        String8 str = String8(data.readString16());
        LOGD("call funcion with %s",str.string());
        return SUCCESS;
    }

    status_t NativeServiceSampleService::sampleGetFunction(const Parcel&data, Parcel* reply){
        LOGD("get function ");
        String16 hello = String16("this is message from service");
        reply->writeString16(hello);
        return SUCCESS;
    }

    status_t NativeServiceSampleService::getMacByInterfaceName(const Parcel &data, Parcel *reply)
    {
        String16 ifname = data.readString16();
        LOGV("get mac of ifname: %s",String8(ifname).string());
        String8 path = String8("/sys/class/net/");
        path += String8(ifname);
        path += String8("/address");
        FILE* fp = fopen(path.string(),"ro");
        if(fp == NULL){
            LOGE("open %s failed(%d): %s",path.string(),errno,strerror(errno));
            return FAILED;
        }

        char mac[18] = {0};
        size_t readsize = fread(mac,1,17,fp);
        if(readsize != 17){
            LOGE("read mac failed");
            return FAILED;
        }
        String16 result = String16(mac);
        reply->writeString16(result);
        fclose(fp);
        return SUCCESS;
    }

    int NativeServiceSampleService::getCallingPid()
    {
        if (IPCThreadState::self()->isServingCall()) {
            return IPCThreadState::self()->getCallingPid();
        }
        return IPCThreadState::self()->getCallingPid();
    }

    int NativeServiceSampleService::getCallingUid()
    {
        if (IPCThreadState::self()->isServingCall()) {
            return IPCThreadState::self()->getCallingUid();
        }
        return IPCThreadState::self()->getCallingUid();
    }
}

我在服务端初始化的时候将唯一的 code 和唯一的函数指针保存进 map ,在 invoke 里面判断如果有相应的 code 存在,则调用相应的函数,没有则直接返回。

这里的 code 则是一个枚举,实际上是双方约定的相同的正整数就可以。

//Cmdid.h
#ifndef _CMDID_H_
#define _CMDID_H_
#include "INativeServiceSample.h"
namespace android{
    enum ENUME_CMD{
        SAMPLE_CALL = IBinder::FIRST_CALL_TRANSACTION,
        SAMPLE_GET,
        SAMPLE_GET_MAC
    };
}

#endif

开启服务

//server.cpp
#include "NativeServiceSampleService.h"
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <utils/String8.h>
#include <utils/String16.h>

int main(int argc, char **argv)
{
    LOGI("start DeviceMacService");
    android::defaultServiceManager()->addService(android::String16(NATIVESERVICE_NAME), new android::NativeServiceSampleService());
    android::ProcessState::self()->startThreadPool();
    android::IPCThreadState::self()->joinThreadPool();
}

客户端调用服务

//client.cpp
#include "NativeServiceSampleService.h"
#include "EnumeCmdid.h"
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <utils/String8.h>
#include <utils/String16.h>

using namespace android;

sp<INativeServiceSample> getService() {
    sp<IServiceManager> sm = defaultServiceManager();
    if (sm == NULL) {
        LOGE("can not get service manager");
    }
    sp<IBinder> binder = sm->getService(String16(NATIVESERVICE_NAME));
    if (binder == NULL) {
        LOGE("can not get service");
    }
    sp<INativeServiceSample> service = interface_cast<INativeServiceSample>(binder);
    if (service == NULL) {
        LOGE("can not cast interface");
    }
    return service;
    return nullptr;
}

int main(int argc,char** argv){
    sp<INativeServiceSample> service = getService();

    {
        Parcel data, reply;
        String16 str = String16("str from client");
        data.writeString16(str);
        int ret = service->invoke(android::SAMPLE_CALL, data, &reply,0);
        if(ret == 0){
            LOGI("invoke value 0");
        }
    }

    {
        Parcel data, reply;
        int ret = service->invoke(android::SAMPLE_GET, data, &reply,0);
        if(ret == 0){
            LOGI("invoke value 0");
        }
        String8 str = String8(reply.readString16());
        LOGI("from str %s",str.string());
    }

    {
        if (argc >= 2)
        {
            Parcel data, reply;
            data.writeString16(String16(argv[1]));
            int ret = service->invoke(android::SAMPLE_GET_MAC, data, &reply, 0);
            if (ret == 0)
            {
                LOGI("get mac %s's mac address is %s", argv[1], String8(reply.readString16()).string());
            }
            else
            {
                LOGE("call get mac function failed");
            }
        }
    }
    return 0;
}

编译

使用下面的 Android.bp

cc_defaults{
    name: "nativeservicesample_defaults",
    srcs:[
        "INativeServiceSample.cpp"
    ],
    shared_libs: [
        "libutils",
        "libcutils",
        "libbinder",
        "libhardware",
        "liblog"
    ],

    include_dirs: [
        "frameworks/native/include",
        "system/core/include",
        "system/libhwbinder/include"
    ],
    cflags: [
        "-Wall",
        "-Wextra",
        "-Werror",
        "-Wno-ignored-qualifiers",
        "-Wno-unused-parameter",
    ],
}

cc_binary{
    name: "server",
    defaults: [
        "nativeservicesample_defaults",
    ],
    srcs: [
        "NativeServiceSampleService.cpp",
        "server.cpp",
    ],
}

cc_binary{
    name: "client",
    defaults: [
        "nativeservicesample_defaults",
    ],
    srcs: [
        "client.cpp",
    ]
}

编译完成之后,将 out/xxxx/system/bin/ 里面的 server 和 client ,推到 Android 设备上(Android studio里面的虚拟机也可以),执行server,然后执行 client ,通过 logcat -s NativeService 就可以看见相应的打印了。

FAQ

安卓 app 如何调用服务?

由于native层定义的 invoke 交给 java 层调用有些麻烦,我们可以直接获取到 Binder 对象之后,调用 Binder 对象的 transcat 函数,将需要的参数传入即可。
下面是一个示范

// NativeService.java
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

public class NativeService {
    private static final String SERVICE_NAME = "nativeservice";

    public static boolean invoke(int code,Parcel data,Parcel reply) throws RemoteException {
        IBinder service = ServiceManager.getService(SERVICE_NAME);
        if(service != null){
            Parcel send = Parcel.obtain();
            send.writeInterfaceToken(SERVICE_NAME);
            send.appendFrom(data,0,data.dataSize());
            boolean result = service.transact(code,send,reply,0);
            send.recycle();
            return result;
        }
        Log.e("shell","service not running");
        return false;
    };
}

code 的值与 native 层定义的 cmdid 一致,从 1 开始。

onTransact 的返回值有什么讲究吗,如果service仅仅由C++层的函数调用,例如示例里的 invoke

        int invoke(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
        {
            Parcel send;
            send.writeInterfaceToken(getInterfaceDescriptor());
            send.appendFrom(&data, 0, data.dataSize());
            return remote()->transact(code, send, reply);
        }

这样的话,我们可以直接拿到 onTransact 的返回值,返回值的意义取决于双方的约定,如果 service 还准备给 Java 层调用,则不可以随意返回,原因是通过 IBinder.transact(...) 的方式调用service,返回值已经被 BinderProxy处理过了,看下面这一段

//frameworks/base/core/jni/android_util_Binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......

    //printf("Transact from Java code to %p sending: ", target); data->print();
    status_t err = target->transact(code, *data, reply, flags);
    //if (reply) printf("Transact from Java code to %p received: ", target); reply->print();

    if (kEnableBinderSample) {
        if (time_binder_calls) {
            conditionally_log_binder_call(start_millis, target, code);
        }
    }

    if (err == NO_ERROR) {
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }

    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}

在这里可以看到,为 NO_ERROR 时返回了 true,为 UNKNOWN_TRANSACTION 时返回了false,都不是时将走到 signalExceptionForError(...)

void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        case UNKNOWN_ERROR:
            jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
            break;
        case NO_MEMORY:
            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
            break;
        case INVALID_OPERATION:
            jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
            break;
        case BAD_VALUE:
            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
            break;
        case BAD_INDEX:
            jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL);
            break;
        case BAD_TYPE:
            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
            break;
        case NAME_NOT_FOUND:
            jniThrowException(env, "java/util/NoSuchElementException", NULL);
            break;
        case PERMISSION_DENIED:
            jniThrowException(env, "java/lang/SecurityException", NULL);

走到这里来之后,调用service的app将发生异常。当然 service 可以根据调用方的不同的参数来返回相应的值来让调用方进入不同的状态。

一个肥肠完整的示例: https://www.jianshu.com/p/46dfce294a4f

posted @ 2022-02-20 19:45  SupperMary  阅读(1256)  评论(0编辑  收藏  举报