如何使用Jni从APP(JAVA)调用到C++→HAL(C)并回调到JAVA层

前言:本文提供在Android系统如何使用jni进行JAVA到C++的调用,再从HAL层回调C++→JAVA

1. JAVA层

1.1 首先是构造一个编译APP的Android.mk

1.2 你需要加载你编写的jni lib库,并声明native方法 Jni_testcallback.java

2. C++层

2.1 在C++你需要实现一个刚刚提到的libtest_jni库文件,首先是一个Android.mk

2.2 然后是你的libtest_jni.cpp

3. HAL (C)

3.1 如上一步,当注册回调函数被传递给Hal层时,我们创建一个线程,在线程里面去不断获取数据

3.2 Hal层的头文件:test_hal.h

4.  可能踩到的坑



1. JAVA层

1.1 首先是构造一个编译APP的Android.mk

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

# Select source code
LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_CFLAGS += -DANDROID_PLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)

modules_to_install_sky += Test

LOCAL_PACKAGE_NAME := Test
LOCAL_PRIVILEGED_MODULE := true


# Each update should be signed by OEM
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform

# 你需要添加依赖你编写的jni库
LOCAL_JNI_SHARED_LIBRARIES := libtest_jni

# Disable dexpreopt
LOCAL_DEX_PREOPT := false

# Disable proguard
LOCAL_PROGUARD_ENABLED := disabled

# Resource directory
# Make sure the generated resource will be added to the apk.
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res

# AndroidManifest file
LOCAL_FULL_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml

include $(BUILD_PACKAGE)

##################################################
# Use the following include to make our test so.
include $(call all-makefiles-under, $(LOCAL_PATH))


1.2 你需要加载你编写的jni lib库,并声明native方法 Jni_testcallback.java

(可以直接在app添加代码,这是一个简单的demo)

//你应该先调用Jni_testcallback .getObjectClass();

public class Jni_testcallback extends Application {
    private static final String TAG = "[APP Jni_testcallback]";
    static {
        //注意test_jni为你编写的lib库名称,库名称应该为libtest_jni,加载不用补充lib
        System.loadLibrary("test_jni");
    }

    //声明一个native 方法,用于传递jni环境参数
    public native void getObjectClass();

    //编写一个JAVA层的回调函数,接受底层传递的参数
    public void  Jni_testcallback(String score, String data) {
            //这里可以做你想做的
            Log.d(TAG, "APP jni score : " + score);
    		Log.d(TAG, "APP jni data : " + data);
        }
 
    }

}

2. C++层

2.1 在C++你需要实现一个刚刚提到的libtest_jni库文件,首先是一个Android.mk

//每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。
//宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。
LOCAL_PATH:= $(call my-dir)

//在描述每个模块之前,必须声明(重新声明)此变量。
include $(CLEAR_VARS)

//这是jni必须依赖的头文件定义
LOCAL_C_INCLUDES += \
		$(JNI_H_INCLUDE)						\

//你的代码
LOCAL_SRC_FILES := \
		libtest_jni.cpp

//可能需要依赖的共享库
LOCAL_SHARED_LIBRARIES := \
		libhardware						\
		libbinder						\
		liblog							\
		libbinder						\
		libutils						\
		libcutils						\
		libnativehelper					\
		libandroid_runtime

//可能需要忽略的警告/报错
LOCAL_CFLAGS += -Wno-unused-parameter -Wunused-variable

//lib库名称
LOCAL_MODULE := libtest_jni

//该模块在所有版本下都编译
LOCAL_MODULE_TAGS := optional

//编译
include $(BUILD_SHARED_LIBRARY)

2.2 然后是你的libtest_jni.cpp

如下,

1. JAVA层在加载libtest_jni.so时会调用jni标准函数JNI_OnLoad获取jni的环境变量,注册jni方法getObjectClass

2. 当JAVA层调用getObjectClass时会将携带Testcallback_jni的函数指针注册到Hal层

#include <hardware/test_hal.h>        //该头文件为你的hal库头文件
#include <hardware/hardware.h>
#include <utils/Log.h>
#include <iostream>
#include <sstream>
#include <string>
#include <jni.h>

#define LOG_TAG "[test_jni]"

//你在java文件声明的类
#define  JAVA_CLASS "com/android/Test/Jni_testcallback"

int sFirstInitFlag = 0;
JavaVM *g_VM  = nullptr;
jobject g_obj  = nullptr;
test_jni_t* jni_callBack;
test_module_t* test_module = nullptr;

//由HAL层线程回调的jni函数,在这里实现对java方法的获取和调用
void Testcallback_jni(char *score, char *value) {
    JNIEnv *env = nullptr;

    if (g_VM == NULL) {
        ALOGE(LOG_TAG"g_VM == 0");
        return;
    }

    int getEnvStat = (*g_VM).GetEnv((void **) &env,
                                    JNI_VERSION_1_6);
    if (getEnvStat == JNI_EDETACHED) {
        ALOGW(LOG_TAG"getEnvStat == 0");
        if ((*g_VM).AttachCurrentThread(&env, NULL) != 0) {
            ALOGE(LOG_TAG"jni thread fail !!");
            return;
        } else {
            ALOGD(LOG_TAG"jni ok");
        }
    }

    //通过全局变量g_obj 获取到要回调的类
    jclass javaClass = (*env).GetObjectClass(g_obj);
    if (javaClass == 0) {
        ALOGE(LOG_TAG"javaclass == 0");
        ALOGD(LOG_TAG"DEATACH :%d", g_VM->DetachCurrentThread());
        return;
    }

    //获取要回调的方法ID
    jmethodID javaCallbackId = (*env).GetMethodID(
            javaClass,//(*env)->GetMethodID(env,jCarplayManager ,
            "Jni_testcallback", "(Ljava/lang/String;Ljava/lang/String;)V");
    //这里的方法签名需要留意一下

    if (javaCallbackId == NULL) {
        ALOGE(LOG_TAG"javaCallbackId == 0");
        ALOGD(LOG_TAG"DEATACH :%d", g_VM->DetachCurrentThread());
        return;
    } else {
        ALOGD(LOG_TAG"Jni ready ");
    }

    //执行回调
    (*env).CallVoidMethod(g_obj, javaCallbackId,env->NewStringUTF(score), env->NewStringUTF(value));
    sFirstInitFlag = 0;
}

//JAVA层声明的native 方法
void getObjectClass(JNIEnv *env, jobject thiz) {

    jni_callBack = new test_jni_t();
    jni_callBack->testjni_callback_cb = Testcallback_jni;

    if (sFirstInitFlag == 0)
    {
        const hw_module_t* hw_module = nullptr;
        env->GetJavaVM(&g_VM);

        g_obj = env->NewGlobalRef(thiz);
        if (env != NULL && g_obj != NULL) {
            ALOGD(LOG_TAG"AUDIO INI OK !!");
        }

        int err = hw_get_module(TEST_HARDWARE_MODULE_ID, &hw_module);
        if (err == 0) {
            ALOGE(LOG_TAG" Set test_hal");
            if (!hw_module->methods || !hw_module->methods->open) {
                test_module = reinterpret_cast<test_module_t*>(
                    const_cast<hw_module_t*>(hw_module));
            } else {
                err = hw_module->methods->open(
                    hw_module, TEST_HARDWARE_MODULE_ID,
                    reinterpret_cast<hw_device_t**>(test_module));
                if (err) {
                    ALOGE(LOG_TAG" %s: HwBinder failed to load legacy Hal\n",
                        __func__);
                }
            }
            test_module->TestInit(test_module, "1111");
            test_module->Testregistercallbacks(jni_callBack);
        } else {
            ALOGE(LOG_TAG" %s: hw_get_module (%s) failed: %d\n", __func__, TEST_HARDWARE_MODULE_ID, err);
        }
        ALOGD(LOG_TAG"--xxxxOpen succeed--");
        sFirstInitFlag = 1;
    } else {
        ALOGD(LOG_TAG"--xxxxOpen error--");
    }
    
    ALOGD(LOG_TAG" getjavaclass exit");
}

//映射JAVA方法
static JNINativeMethod sMethods[] = {
    {"getObjectClass",          "()V",                       (void *) getObjectClass}
    };

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) {
//注册上面实现的的native 方法(getObjectClass)
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        ALOGD(LOG_TAG" FIND CLASS FAIL");
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        ALOGD(LOG_TAG"REGISTER FAIL");
    }

    return JNI_TRUE;
}

int register_com_android_test_meacallback(JNIEnv* env) {
    int re = registerNativeMethods(env, JAVA_CLASS, sMethods,
                                   sizeof(sMethods) / sizeof(sMethods[0]));
    return re;
}

//标准的jni函数,在JAVA层加载该库时会自动执行
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved) {
    int status;
    JNIEnv *env = nullptr;

    ALOGD(LOG_TAG" %s: entry!\n", __func__);

    if (jvm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }

    status = register_com_android_test_meacallback(env);
    if (status < 0) {
      ALOGE(LOG_TAG"jni   registration failure, status: %d", status);
      return JNI_ERR;
    }

    return JNI_VERSION_1_6;

}

3. HAL (C)

3.1 如上一步,当注册回调函数被传递给Hal层时,我们创建一个线程,在线程里面去不断获取数据

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define LOG_TAG "[text_hal]"
#include <utils/Log.h>

#include <hardware/test_hal.h>
#include <hardware/hardware.h>

#define UNUSED_ARGUMENT __attribute((unused))

// 全局变量
pthread_t th;
int callback_exit_flag;
static test_jni_t* mCallback;

void get_drv()
{
    //做你想做的事情,比如获取某些底层数据
    //如果他需要一定时间准备的话

    if(1) {//得到数据后调用回调函数
        mCallback.testjni_callback_cb(xxx, xxxx);
        callback_exit_flag = 1;
    }

}

void* callback_thread(void *args)
{

    ALOGD(LOG_TAG" %s: Enter!\n", __func__);
    callback_exit_flag = 0;
    mCallback = (test_jni_t*)args;
    while(!callback_exit_flag){
        usleep(300000);
        get_drv();
    }

    ALOGD(LOG_TAG" %s: thread exit !\n", __func__);

    pthread_exit(NULL);
}

//注册回调函数
void test_register_callbacks(test_jni_t* jni_callback)
{
    int ret;

    ALOGD(LOG_TAG" %s: Enter!\n", __func__);
    if(jni_callback == NULL){
        ALOGE(LOG_TAG" %s: jni_callback is NULL!\n", __func__);
        return;
    }

    //将jni回调函数指针传递入线程函数
    ret = pthread_create(&th, NULL, callback_thread, jni_callback);
}

//这是必要的hal模块结构体
test_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag                = HARDWARE_MODULE_TAG,
        .module_api_version = TEST_MODULE_API_VERSION_1_0,
        .hal_api_version    = HARDWARE_HAL_API_VERSION,
        .id                 = TEST_HARDWARE_MODULE_ID,
        .name               = MODULE_NAME,
        .author             = MODULE_AUTHOR,
        .methods            = &test_module_methods,
    },

    .Testregistercallbacks       = test_register_callbacks
};

3.2 Hal层的头文件:test_hal.h

#include <stdbool.h>
#include <sys/cdefs.h>
#include <hardware/hardware.h>

__BEGIN_DECLS

//模块ID
#define TEST_HARDWARE_MODULE_ID "test_hal"

//版本号
#define TEST_MODULE_API_VERSION_1_0 HARDWARE_MODULE_API_VERSION(1, 0)

typedef void (*java_Testcallback)(char *score, char *value);

typedef struct {
    java_Testcallback testjni_callback_cb;
} test_jni_t;

typedef struct test_module {

    void (*testregistercallbacks)(test_jni_t* jni_callback);

} test_module_t;

4.  可能踩到的坑

1. 关于JAVA层对lib库的加载

        a) lib库的命名;lib库的名字格式前必须要有lib字段,即在该示例中lib库文件全名应该是libtest_jni.so。

        b) lib库的路径;Android系统默认会在system/lib64等路径去查找并加载so。

        c) lib库的权限;默认最少需要root:root 、0644的用户分组权限,否则可能存在打开失败的问题。

2. 关于jni使用

        a) 在so被加载后,JNI_OnLoad函数会自动执行,当然你也可以不重写它。

        b) 本文的示例中,需要映射完JAVA→C++的调用后,APP先调用native方法,至C++层获取jni必须的变量后,在进行C++→JAVA的获取调用。

        c) JAVA方法签名,不同的方法类型及参数都有不同的方法签名,编译完成后,可以在out目录查找生成的对应class文件,使用命令查看对应的方法签名 javap -s Jni_testcallback.class

        d) 字符串的转化,从遇到的编译报错来看,在执行jni回调函数时发现(*env).CallVoidMethod无法接受String的参数,他需要一个jstring类型,在网上查到了许多方法,

通过jstring jtemp = env->NewStringUTF(temp.c_str());会发现执行到NewStringUTF后会崩溃,

网上的解释大多是NewStringUTF使用完没有释放或者编码格式的异常,本文直接将NewStringUTF构建的jstring传给JAVA层,可以避免这个问题

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值