JNI数据类型及与Java数据类型的映射关系
Java数据类型 | JNI数据类型 |
---|---|
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
java数据类型与签名类型的对应关系
Java类型 | 类型签名 |
---|---|
boolean | Z |
byte | B |
int | I |
char | C |
short | S |
long | L |
float | F |
double | D |
void | V |
数组 | [类型签名,比如String[] 是[Ljava/lang/String; |
类 | L全限定名;,比如String, 其签名为Ljava/lang/String;(注意后面有个分号) |
静态注册函数
根据函数名来建立 java 方法与 JNI 函数的一一对应关系;
命名规则如下:Java+方法的全路径(路径中一定要用“_”代替Java全路径里面的"“.”)
固定模式1:每个函数头部必须包含extern “C” JNIEXPORT <函数的返回类型> JNICALL
固定模式2:每个函数的入参必须包含JNIEnv env, jclass clazz
jobject与jclass通常作为JNI函数的第二个参数,当所声明Native方法是静态方法时,对应参数jclass,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject
在MainActivity里面定义了一个native方法:
public native String stringFromJNI();
按照命名对应规则,JNI层的函数注册为:
extern "C" JNIEXPORT jstring JNICALL
Java_com_anniljing_jnidemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGD("Get native message success");
return env->NewStringUTF(hello.c_str());
}
问题1:正常情况下,光标放在native方法上,然后使用快捷键alt+enter会有Create JNI function for setMode提示,选择Create JNI function for setMode,会快速帮我们创建对应的cpp文件,
如果没有的话,需要我们手动创建,手动输入是不存在的,还是把光标放在对应的native方法之上,复制提示的内容,然后加上头部固定模式就可以了。
动态注册函数
原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;
实现流程:
1、实现 JNI_OnLoad 方法,该方法在jni.h的头文件中定义,其中还定义了JNI_OnUnload方法,在加载动态库后,执行动态注册;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
}
2、调用 FindClass 方法,获取 java 对象;
jclass jcls;
jcls = env->FindClass(name);
if (jcls == nullptr) {
return JNI_FALSE;
}
3、利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
name为java层的方法名,signature为java层的参数和返回值的类型签名,fnPtr为jni层方法名
static JNINativeMethod gMethods[] = {
{"sum", "(II)I", (void *) sum},
{"getNativeString", "()Ljava/lang/String;", (void *) getMessage}
};
4、调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;
if (env->RegisterNatives(jcls, methods, nMethods) < 0) {
return JNI_FALSE;
}
JNI层访问Java层类属性
JNI获取属性方法 | 含义 |
---|---|
GetFieldID(jclass clazz, const char* name, const char* sig) | 获取属性Id,形参:clazz-java类,name-java类属性名,sig-java类属性类型签名 |
GetObjectField(jobject obj, jfieldID fieldID) | 获取属性为对象的 |
GetBooleanField(jobject obj, jfieldID fieldID) | 获取类型为布尔值的属性 |
GetByteField(jobject obj, jfieldID fieldID) | 获取类型为byte的属性 |
GetCharField(jobject obj, jfieldID fieldID) | 获取类型为Char的属性 |
GetShortField(jobject obj, jfieldID fieldID) | 获取类型为Short的属性 |
GetIntField(jobject obj, jfieldID fieldID) | 获取类型为Int的属性 |
GetLongField(jobject obj, jfieldID fieldID) | 获取类型为long的属性 |
GetFloatField(jobject obj, jfieldID fieldID) | 获取类型为float的属性 |
GetDoubleField(jobject obj, jfieldID fieldID) | 获取类型为double的属性 |
JNI层设置Java层类属性值
JNI设置属性方法 | 含义 |
---|---|
SetObjectField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Object的属性值 |
SetBooleanField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Boolean的属性值 |
SetByteField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Byte的属性值 |
SetCharField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Char的属性值 |
SetShortField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Short的属性值 |
SetIntField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Int的属性值 |
SetLongField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Long的属性值 |
SetFloatField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Float的属性值 |
SetDoubleField(jobject obj, jfieldID fieldID, jobject value) | 设置类型为Double的属性值 |
第一步、获取到Java类
jclass jc = env->GetObjectClass(people);
第二步、GetFieldID方法获取到Java层类属性
jfieldID fidAge = env->GetFieldID(jc, "age", "I");
第三步、调用SetXXXField()方法,为java层的类属性设置值
env->SetIntField(people, fidAge, 18);
JNI层访问Java层类成员方法
第一步、获取Java类
jclass jc = env->GetObjectClass(people);
第二步、调用GetMethodID()方法,获取jMethodID
jmethodID jMethod = env->GetMethodID(jc, "doIntroduce", "()V");
第三步、调用CallVoidMethod()方法,调用Java类成员方法
env->CallVoidMethod(people, jMethod);
问题1:新增加的AndroidLog.h的头文件一直无法预编译<android/log.h>和<jni.h>头文件
AndroidLog.h
//头文件只有被其他CPP文件依赖以后才能正常导入
#include <android/log.h>
#include <jni.h>
#ifndef JNIDEMO_ANDROIDLOG_H
#define JNIDEMO_ANDROIDLOG_H
#define TAG "JniDemo"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型
#endif //JNIDEMO_ANDROIDLOG_H
<android/log.h>
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
/**
* @addtogroup Logging
* @{
*/
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/cdefs.h>
#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN)
#define __INTRODUCED_IN(x)
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* Android log priority values, in increasing order of priority.
*/
typedef enum android_LogPriority {
/** For internal use only. */
ANDROID_LOG_UNKNOWN = 0,
/** The default priority, for internal use only. */
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
/** Verbose logging. Should typically be disabled for a release apk. */
ANDROID_LOG_VERBOSE,
/** Debug logging. Should typically be disabled for a release apk. */
ANDROID_LOG_DEBUG,
/** Informational logging. Should typically be disabled for a release apk. */
ANDROID_LOG_INFO,
/** Warning logging. For use with recoverable failures. */
ANDROID_LOG_WARN,
/** Error logging. For use with unrecoverable failures. */
ANDROID_LOG_ERROR,
/** Fatal logging. For use when aborting. */
ANDROID_LOG_FATAL,
/** For internal use only. */
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
int __android_log_write(int prio, const char* tag, const char* text);
int __android_log_print(int prio, const char* tag, const char* fmt, ...)
__attribute__((__format__(printf, 3, 4)));
int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap)
__attribute__((__format__(printf, 3, 0)));
void __android_log_assert(const char* cond, const char* tag, const char* fmt, ...)
__attribute__((__noreturn__)) __attribute__((__format__(printf, 3, 4)));
/**
* Identifies a specific log buffer for __android_log_buf_write()
* and __android_log_buf_print().
*/
typedef enum log_id {
LOG_ID_MIN = 0,
/** The main log buffer. This is the only log buffer available to apps. */
LOG_ID_MAIN = 0,
/** The radio log buffer. */
LOG_ID_RADIO = 1,
/** The event log buffer. */
LOG_ID_EVENTS = 2,
/** The system log buffer. */
LOG_ID_SYSTEM = 3,
/** The crash log buffer. */
LOG_ID_CRASH = 4,
/** The statistics log buffer. */
LOG_ID_STATS = 5,
/** The security log buffer. */
LOG_ID_SECURITY = 6,
/** The kernel log buffer. */
LOG_ID_KERNEL = 7,
LOG_ID_MAX,
/** Let the logging function choose the best log target. */
LOG_ID_DEFAULT = 0x7FFFFFFF
} log_id_t;
int __android_log_buf_write(int bufID, int prio, const char* tag, const char* text);
int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...)
__attribute__((__format__(printf, 4, 5)));
struct __android_log_message {
size_t
struct_size; /** Must be set to sizeof(__android_log_message) and is used for versioning. */
int32_t buffer_id; /** {@link log_id_t} values. */
int32_t priority; /** {@link android_LogPriority} values. */
const char* tag; /** The tag for the log message. */
const char* file; /** Optional file name, may be set to nullptr. */
uint32_t line; /** Optional line number, ignore if file is nullptr. */
const char* message; /** The log message itself. */
};
typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
typedef void (*__android_aborter_function)(const char* abort_message);
#if !defined(__ANDROID__) || __ANDROID_API__ >= 30
void __android_log_write_log_message(struct __android_log_message* log_message) __INTRODUCED_IN(30);
void __android_log_set_logger(__android_logger_function logger) __INTRODUCED_IN(30);
void __android_log_logd_logger(const struct __android_log_message* log_message) __INTRODUCED_IN(30);
void __android_log_stderr_logger(const struct __android_log_message* log_message)
__INTRODUCED_IN(30);
void __android_log_set_aborter(__android_aborter_function aborter) __INTRODUCED_IN(30);
void __android_log_call_aborter(const char* abort_message) __INTRODUCED_IN(30);
void __android_log_default_aborter(const char* abort_message) __attribute__((noreturn))
__INTRODUCED_IN(30);
int __android_log_is_loggable(int prio, const char* tag, int default_prio) __INTRODUCED_IN(30);
int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio)
__INTRODUCED_IN(30);
int32_t __android_log_set_minimum_priority(int32_t priority) __INTRODUCED_IN(30);
int32_t __android_log_get_minimum_priority(void) __INTRODUCED_IN(30);
void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30);
#endif
#ifdef __cplusplus
}
#endif
/** @} */
该头文件是系统下的log.h的头文件内容,里面定义了我们AndroidLog.h里面使用的__android_log_print方法,以及android系统log的等级。
typedef enum android_LogPriority {
/** For internal use only. */
ANDROID_LOG_UNKNOWN = 0,
/** The default priority, for internal use only. */
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
/** Verbose logging. Should typically be disabled for a release apk. */
ANDROID_LOG_VERBOSE,
/** Debug logging. Should typically be disabled for a release apk. */
ANDROID_LOG_DEBUG,
/** Informational logging. Should typically be disabled for a release apk. */
ANDROID_LOG_INFO,
/** Warning logging. For use with recoverable failures. */
ANDROID_LOG_WARN,
/** Error logging. For use with unrecoverable failures. */
ANDROID_LOG_ERROR,
/** Fatal logging. For use when aborting. */
ANDROID_LOG_FATAL,
/** For internal use only. */
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
解决方法:
先创建空的AndroidLog.h文件,然后在其他的cpp源文件加入其预编译的头文件,重新编译一下,AndroidLog头文件再重新加入相关的头文件,编译即可通过
问题2:新增加jni_dynamic_load.cpp,实现动态注册jni,CMakeList.txt的编写如下:
新增了如下代码:
add_library(
dynamicLoad-lib
SHARED
jni_dynamic_load.cpp
)
但是编译的时候,抛出之前的AndroidLog.h文件未定义的异常
解决方法:
1、把jni_dynamic_load.cpp放到 native-lib里面,CMakeList.txt如下:
cmake_minimum_required(VERSION 3.10.2)
project("jnidemo")
add_library(
native-lib
SHARED
native-lib.cpp
jni_dynamic_load.cpp
)
find_library(
log-lib
log
)
include_directories(${CMAKE_HOME_DIRECTORY}/base)
target_link_libraries(
native-lib
${log-lib})
这样的话,就放在了一个库里面。
2、但是这不是我当初想要的效果,我是想生成两个库的。原因肯定是在连接库的地方出现了问题的,于是CMakeList.txt改为如下内容:
cmake_minimum_required(VERSION 3.10.2)
project("jnidemo")
add_library(
native-lib
SHARED
native-lib.cpp
)
add_library(
dynamicLoad-lib
SHARED
jni_dynamic_load.cpp
)
find_library(
log-lib
log
)
include_directories(${CMAKE_HOME_DIRECTORY}/base)
target_link_libraries(
native-lib
${log-lib})
target_link_libraries(
dynamicLoad-lib
${log-lib}
)
核心代码在于最下面第二个target_link_libraries,就是做了dynamicLoad-lib库与Log库的连接。
target_link_libraries(
dynamicLoad-lib
${log-lib}
问题3:illegal class name
cpp文件是无法识别“.”的路径,要把“.”换成“/”即可
#define JAVA_CLASS "com/anniljing/jnidemo/JniDynamicLoad"
GitHub源码
官方地址:https://docs.oracle.com/en/java/javase/21/docs/specs/jni/index.html