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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM