Android Native Service 示例 第二篇

在安卓里面实现一个 native 的系统服务,不仅可以通过定义 Interface ,还可以通过直接继承 BBinder 来实现。本篇文章即讲述如何通过继承 Bbinder 来实现一个系统服务,并且讲述如何在 native 上使用 binder 的 linktoDeath 机制

首先列出需要包含的头文件

// CommonHeader.h
#ifndef _COMMON_HEADER_H_
#define _COMMON_HEADER_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 <log/log_main.h>
#include "Cmdid.h"
#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "NativeService"
#define NATIVESERVICE_NAME "nativeservice"
#endif

这里为了方便,我将可能用到的头文件都聚在一起了。

服务端

然后服务端的声明

//NativeServiceSampleService.h
#ifndef _NATIVE_SERVICE_SAMPLE_SERVICE_H_
#define _NATIVE_SERVICE_SAMPLE_SERVICE_H_

#include "CommonHeader.h"
#include <map>

namespace android
{
    enum STATUS {
        SUCCESS = NO_ERROR,
        FAILED = UNKNOWN_TRANSACTION
    };
    typedef status_t (*local_method)(const Parcel &data, Parcel *reply);
    class NativeServiceSampleService : public BBinder {
    public:
        NativeServiceSampleService();
        virtual status_t onTransact(uint32_t code,const Parcel& data, Parcel* reply,uint32_t flags = 0);
        status_t dump(int fd, const Vector<String16> &args);
        int onShellCommand(int in, int out, int err, const Vector<String16> &args);
        virtual status_t invoke(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
        virtual const String16& getInterfaceDescriptor() const{return this->interface;};
    private:
        int shellCommand(int in, int out, int err, const Vector<String16> &args);
        int getCallingPid(){return IPCThreadState::self()->getCallingPid();}
        int getCallingUid(){return IPCThreadState::self()->getCallingUid();}
        std::map<uint32_t,local_method> methods;
        String16 interface = String16(NATIVESERVICE_NAME);
    };
} // namespace android

#endif

这里最重要的依然是 onTransact ,Binder 通信的第一站就是这里,来看看服务端的实现

//NativeServiceSampleService.cpp
#include "NativeServiceSampleService.h"
#include "Cmdid.h"
namespace android
{
    status_t sampleCallFunction(const Parcel&data, Parcel* reply){
        String8 str = String8(data.readString16());
        ALOGD("call funcion called with \"%s\"",str.string());
        return SUCCESS;
    }

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

    status_t getMacByInterfaceName(const Parcel &data, Parcel *reply)
    {
        String16 ifname = data.readString16();
        ALOGV("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){
            ALOGE("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){
            ALOGE("read mac failed");
            fclose(fp);
            return FAILED;
        }
        String16 result = String16(mac);
        reply->writeString16(result);
        fclose(fp);
        return SUCCESS;
    }

    status_t SampleExitService(const Parcel &data,Parcel *reply)
    {
        ALOGI("service exit now");
        printf("service received exit message\n");
        exit(0);
        return SUCCESS;
    }

    NativeServiceSampleService::NativeServiceSampleService()
    {
        methods[SAMPLE_CALL] = &sampleCallFunction;
        methods[SAMPLE_GET] = &sampleGetFunction;
        methods[SAMPLE_GET_MAC] = &getMacByInterfaceName;
        methods[SAMPLE_EXIT_SERVER] = &SampleExitService;
    }

    status_t NativeServiceSampleService::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(INativeServiceSampleService, data, reply);
            return invoke(code, data, reply, flags);
        }
        else
        {
            return BBinder::onTransact(code, data, reply, flags);
        }
    }

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

    status_t NativeServiceSampleService::onShellCommand(int in, int out, int err, const Vector<String16> &args)
    {
        ALOGI("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++)
        {
            ALOGI("Received No.%zu arg: %s", i, String8(args[i]).string());
        }
        return SUCCESS;
    }

    status_t NativeServiceSampleService::dump(int fd, const Vector<String16> &args)
    {
        ALOGI("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++)
        {
            ALOGI("Received No.%zu arg: %s", i, String8(args[i]).string());
        }
        return SUCCESS;
    }

    status_t NativeServiceSampleService::invoke(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
    {
        std::map<uint32_t, local_method>::iterator iter = methods.find(code);
        ALOGV("uid:%d pid:%d called method %u",getCallingUid(),getCallingPid(),code);
        if (iter != methods.end())
        {
            return (*(iter->second))(data, reply);
        }
        else
        {
            ALOGE("can't find such a method whose cmdid is %u", code);
            return -1;
        }
    }
}

同样的,这里用一个 map 来保存了不同code和对应的函数的指针,服务端接收到调用时,可以判断 code 来直接从 map 里面取到相应的函数指针来调用并返回。
dump 和 shellcommand 是通过 dumpsys servicename 和 cmd servicename 这两种方式,会接收到的特殊 code ,这里同样封装成了接口,以供实现。
这个时候,服务端的实现已经完成了,可以参考下面的办法来启动一个服务端

//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)
{
    printf("start %s\n",NATIVESERVICE_NAME);
    ALOGI("start %s",NATIVESERVICE_NAME);
    android::defaultServiceManager()->addService(android::String16(NATIVESERVICE_NAME), new android::NativeServiceSampleService());
    android::ProcessState::self()->startThreadPool();
    android::IPCThreadState::self()->joinThreadPool();
    return 0;
}

addService 见名知意,android::ProcessState::self()->startThreadPool(); 则是监听Binder通信的各种消息。android::IPCThreadState::self()->joinThreadPool(); 会阻塞在这里,通常我们是希望服务端保持运行的。

客户端

客户端是调用方,调用方最后调用的都是 Binder 的 transact ,当一个服务没有通过 interface 来声明的时候,我们可以直接调用 Binder 的 transact 来调用服务端。这里我们为客户端加上监听服务端是否退出的机制,这样客户端在调用过程中,如果服务端推出了,那么后续的调用则不再接收。

首先是 DeathRecipient ,与 java 层的类似,native 的也是继承相应的类

//NativeServiceSampleDeathRecipient.h
#ifndef _NATIVE_SERVICE_SAMPLE_DEATH_RECIPIENT_H_
#define _NATIVE_SERVICE_SAMPLE_DEATH_RECIPIENT_H_

#include "CommonHeader.h"
#include <mutex>
#include <atomic>

namespace android{
    class NativeServiceSampleDeathRecipient : public IBinder::DeathRecipient
    {
        public:
        NativeServiceSampleDeathRecipient(){mValidVar.store(invalid,std::memory_order_relaxed);};
        bool setValid(){
            return mValidVar.compare_exchange_strong(invalid,valid);
        }
        bool setInvalid(){
            return mValidVar.compare_exchange_strong(valid,invalid);
        }
        bool isValid(){
            return mValidVar.load();
        }
        private:
        virtual void binderDied(const wp<IBinder>& who) {
            ALOGE("service died. set service invalid");
            setInvalid();
        };
        std::atomic_bool mValidVar;
        bool valid = true;
        bool invalid = false;
    };
}

#endif

定义一个类,继承于 IBinder::DeathRecipient ,并覆写 binderDied 。

来看客户端的声明

//NativeServiceSampleClient.h
#ifndef _NATIVE_SERVICE_SAMPLE_CLIENT_H_
#define _NATIVE_SERVICE_SAMPLE_CLIENT_H_
#include "CommonHeader.h"
#include "NativeServiceSampleDeathRecipient.h"
namespace android{
    class NativeServiceSampleClient{
        public:
        NativeServiceSampleClient();
        ~NativeServiceSampleClient();
        status_t invoke(uint32_t code, const Parcel &data, Parcel *reply,int flag);
        status_t invoke(uint32_t code, const Parcel &data, Parcel *reply);
        private:
        sp<IBinder> service;
        sp<NativeServiceSampleDeathRecipient> mdeathRecipient;
    };
}
#endif

看客户端的实现

//NativeServiceSampleClient.cpp
#include "NativeServiceSampleClient.h"

namespace android{
    NativeServiceSampleClient::NativeServiceSampleClient(){
        sp<IServiceManager> sm = defaultServiceManager();
        if (sm == NULL)
        {
            ALOGE("can not get service manager");
            return;
        }
        service = sm->getService(String16(NATIVESERVICE_NAME));
        if (service == NULL)
        {
            ALOGE("can not get service");
            return;
        }
        mdeathRecipient = new NativeServiceSampleDeathRecipient();
        if(service->linkToDeath(mdeathRecipient) != NO_ERROR)
        {
            ALOGE("link to death failed");
            return;
        }
        ALOGI("client link to death success");
        if(!mdeathRecipient->setValid()){
            ALOGE("set death recipient valid failed");
        }
        if(mdeathRecipient->isValid()){
            ALOGI("service is running");
        }else{
            ALOGE("service is not running and why ?");
        }
    }

    NativeServiceSampleClient::~NativeServiceSampleClient()
    {
        if(service != nullptr)
        {
            if(mdeathRecipient->isValid())
            {
                if(service->unlinkToDeath(mdeathRecipient) == NO_ERROR)
                    ALOGD("unlinktodeath success");
                else
                    ALOGE("unlinktodeath failed");
            }
            else
            {
                ALOGE("service is not running. need unlinkToDeath ?");
            }
        }
    }

    status_t NativeServiceSampleClient::invoke(uint32_t code, const Parcel &data, Parcel *reply)
    {
        return invoke(code,data,reply,0);
    }

    status_t NativeServiceSampleClient::invoke(uint32_t code, const Parcel &data, Parcel *reply,int flags)
    {
        if (!mdeathRecipient->isValid())
        {
            ALOGE("service is not running");
            return -1;
        }
            
        Parcel send;
        send.writeInterfaceToken(android::String16(NATIVESERVICE_NAME));
        send.appendFrom(&data, 0, data.dataSize());
        return service->transact(code, send, reply,flags);
    }
}

客户端主要干两个事情,定义一个对外提供 transact 调用的接口,以及在构造的时候,为服务端的 binder 对象 linktoDeath ,这样服务端中途退出时,客户端可以知道相应的消息

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

cmdid 依旧是服务端和客户端共同约定的通信 code。

调用服务端可以参考下面的实现

//client.cpp
#include "CommonHeader.h"
#include "NativeServiceSampleClient.h"
#include "cmdline.h"
#include <string>
#include <unistd.h>
using namespace android;

void sampleSendStringToServer(NativeServiceSampleClient &service)
{
    Parcel data, reply;
    
    printf("send \"sample message\" to service\n");
    String16 str = String16("sample message to service\"");
    data.writeString16(str);
    int ret = service.invoke(android::SAMPLE_CALL, data, &reply);
    if (ret == NO_ERROR)
    {
        printf("service has received message\n");
    }
    else
    {
        printf("send msg to service failed\n");
    }
}

void sampleGetStringFromServer(NativeServiceSampleClient &service)
{
    Parcel data, reply;
    int ret = service.invoke(android::SAMPLE_GET, data, &reply);
    if (ret == NO_ERROR)
    {
        String8 str = String8(reply.readString16());
        printf("received msg from server: \"%s\"\n",str.string());
    }
}

void sampleExitServer(NativeServiceSampleClient &service)
{
    Parcel data,reply;
    // no wait reply;
    int ret = service.invoke(android::SAMPLE_EXIT_SERVER, data, &reply,1);
    if(ret == NO_ERROR)
    {
        printf("call service exit function success\n");
    }else{
        printf("call service exit function failed\n");
    }
}

void sampleGetmacByInterfaceName(NativeServiceSampleClient &service,std::string& ifname)
{
    Parcel data, reply;
    data.writeString16(String16(ifname.c_str()));
    int ret = service.invoke(android::SAMPLE_GET_MAC, data, &reply);
    if (ret == NO_ERROR)
    {
        printf("get mac %s's mac address is %s\n",ifname.c_str(), String8(reply.readString16()).string());
    }
    else
    {
        printf("call get mac function failed\n");
    }
}

int main(int argc, char *argv[])
{
    cmdline::parser args;
    args.add("call",'\0',"call sample send msg to service");
    args.add("get",'\0',"call sample get msg from service");
    args.add<std::string>("ifname",'i',"get supplied interface name",false,"");
    args.add("exit",'\0',"call function make service exit and link death recipient");

    args.parse_check(argc,argv);

    NativeServiceSampleClient service;
    ProcessState::self()->startThreadPool();
    if(args.exist("call")){
        sampleSendStringToServer(service);
    }
    if(args.exist("get")){
        sampleGetStringFromServer(service);
    }
    if(args.exist("exit")){
        sampleExitServer(service);
        // listen to wait service died msg;
        sleep(5);
    }
    if(args.exist("ifname")){
        std::string ifname = args.get<std::string>("ifname");
        sampleGetmacByInterfaceName(service,ifname);
    }
    // printf("press any key exit\n");
    // getchar();
    return 0;
}

cmdline.h是一个解析命令行参数的库,见 https://github.com/tanakh/cmdline

在安卓的源码环境中,可以参考以下 Android.bp 来将服务端和客户端的可执行文件都编译出来

cc_defaults{
    name: "nativeservicesample_defaults",
    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: [
        "server.cpp",
        "NativeServiceSampleService.cpp",
    ],
}

cc_library{
    name: "libnativeservicesampleclient",
    srcs: [
        "NativeServiceSampleClient.cpp",
    ],
    defaults: [
        "nativeservicesample_defaults",
    ],
}

cc_binary{
    name: "client",
    defaults: [
        "nativeservicesample_defaults",
    ],
    cflags: [
        "-fexceptions",
    ],
    static_libs: [
        "libnativeservicesampleclient",
    ],
    rtti: true,
    srcs: [
        "client.cpp",
    ],
}

服务端直接执行即可,客户端可以通过 --help 参数来查看帮助。

客户端想要监听服务端退出的消息,除了需要 linktodeath 之外,还需要 ProcessState::self()->startThreadPool();

如何在 java 层调用

通过jni调用

我们可以通过 jni 调用 NativeServiceSampleClient::invoke 来获取返回值,参考实现如下

#include <stdio.h>
#include <android_os_Parcel.h>
#include <binder/Parcel.h>
#include "jni.h"

#include "NativeServiceSampleClient.h"

static android::NativeServiceSampleClient* g_client = nullptr;

static int
invoke(JNIEnv* env , jobject /* thiz */,jint code, jobject dataObj, jobject replyObj, jint flags) {
    ALOGI("invoke called");
    if (dataObj == NULL) {
        // jniThrowNullPointerException(env, NULL);
        return -1;
    }

    android::Parcel* data = android::parcelForJavaObject(env, dataObj);
    if (data == NULL) {
        return -1;
    }
    android::Parcel* reply = android::parcelForJavaObject(env, replyObj);
    if (reply == NULL && replyObj != NULL) {
        return -1;
    }

    if(g_client == nullptr){
        return -1;
    }

    return g_client->invoke(code, *data,reply,flags);
}

static const char *classPathName = "com/example/NativeServiceSample";

static const JNINativeMethod methods[] = {
  {"invoke","(ILandroid/os/Parcel;Landroid/os/Parcel;I)I",(void*)invoke},
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        ALOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        ALOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }

  return JNI_TRUE;
}


// ----------------------------------------------------------------------------

/*
 * This is called by the VM when the shared library is first loaded.
 */

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    ALOGI("Jni_OnLoad called");
    UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv* env = NULL;

    ALOGI("JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed");
        goto bail;
    }
    env = uenv.env;

    if (registerNatives(env) != JNI_TRUE) {
        ALOGE("ERROR: registerNatives failed");
        goto bail;
    }

    if(g_client == nullptr){
        g_client = new android::NativeServiceSampleClient();
    }

    result = JNI_VERSION_1_4;

bail:
    return result;
}

我们新建一个全局的静态变量,在 jni_onload 的时候初始化它,定义了一个用于与 java 层交互的 invoke 函数,此函数的再调用 NativeServiceSampleClient 的 invoke 并获取返回结果
p.s: 需要在源码环境下 link liblog libbinder libandroid_runtime,这种方法需要系统开放私有的库,对应用开放的库见 /system/etc/public.libraries.txt 。

通过安卓的 IBinder 调用

安卓的 android.os.IBinder 本身已经封装成对应的 java api了,系统应用可以通过 ServiceManager.getService 拿到对应的binder对象,参考实现如下

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";
    private boolean valid = false;
    IBinder service = null;
    public NativeService(){
        valid = false;
        service = ServiceManager.getService(SERVICE_NAME);
        if(service != null){
            try{
                service.linkToDeath(()->{
                    Log.e("shell","service died");
                    setInvlaid();
                },0);
                setValid();
            }catch (RemoteException e){
                Log.e("shell","link to death failed",e);
                setInvlaid();
            }
        }
    }

    public boolean invoke(int code,Parcel data,Parcel reply) throws RemoteException {
        if(isValid()){
            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;
    };

    private void setInvlaid(){
        synchronized(this){
            valid = false;
        }
    }

    private void setValid(){
        synchronized(this){
            valid = true;
        }
    }

    private boolean isValid(){
        synchronized(this){
            return valid;
        }
    }
}

p.s: 此方式需要应用为系统应用或反射 ServiceManager 或在源码环境下编译

posted @ 2022-03-05 13:13  SupperMary  阅读(638)  评论(0编辑  收藏  举报