Android JNI入门

JNI数据类型及与Java数据类型的映射关系

Java数据类型JNI数据类型
bytejbyte
charjchar
shortjshort
intjint
longjlong
floatjfloat
doublejdouble

java数据类型与签名类型的对应关系

Java类型类型签名
booleanZ
byteB
intI
charC
shortS
longL
floatF
doubleD
voidV
数组[类型签名,比如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的编写如下:

image
新增了如下代码:

add_library(
        dynamicLoad-lib
        SHARED
        jni_dynamic_load.cpp
)

但是编译的时候,抛出之前的AndroidLog.h文件未定义的异常

555d51a695b861ed316cd118068eefb

解决方法:

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

image
image

cpp文件是无法识别“.”的路径,要把“.”换成“/”即可

#define JAVA_CLASS "com/anniljing/jnidemo/JniDynamicLoad"

GitHub源码
官方地址:https://docs.oracle.com/en/java/javase/21/docs/specs/jni/index.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值