android JNI kotlin 使用 jni

195 阅读5分钟

JNI


  


1、创建 native 方法

public static native String getStringFromNDK()

2、进入class 目录 执行 javah -jni 生成 .h 头文件

3、创建 cpp 文件,导入头文件,实现对应的方法

JNIEXPORT jstring JNICALL Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK

(JNIEnv *env, jobject obj)

4、导入 so 库

static {

System.loadLibrary("ndkdemotest-jni");

}

5、CMake配置

gralde 配置

  


externalNativeBuild {

cmake {

cppFlags ""

}

}

externalNativeBuild {

cmake {

path "CMakeLists.txt"

}

}

  


CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

add_library( 创建动态库

find_library( 发现三方库

target_link_libraries 自己的 so和三方库关联

  
  


kotlin native 方法

external fun getSecretKey(): String

  


object JniHelper {

  


init {

  


//加载 so 库

  


try{

  


System.loadLibrary("native-lib")

  


}catch (e: Exception){

e.printStackTrace()

}

  


}

  


//jni 获取密钥

external fun getSecretKey(): String

}

  


生成.h 头文件

切换到项目全路径 username/SmartKT/app/build/tmp/kotlin-classes/debug kotlin生成 class 文件目录

  


javah -d username/SmartKT/app/src/main/cpp/ com.aaron.aaronkotlin.JniHelper

  


在 目录 username/SmartKT/app/src/main/cpp/ 下生成.h 头文件

  
  
  
  


CMakeLists.txt

  


# cmake最低版本号

cmake_minimum_required(VERSION 3.18.1)

  


# add_library:把一个library添加到工程

add_library(

native-lib

SHARED

native-lib.cpp)

  


# 找到预编译库 log_lib 并link到我们的动态库 native-lib中

find_library( # Sets the name of the path variable.

log-lib

# Specifies the name of the NDK library that

# you want CMake to locate.

log )

  
  


# 首个参数是target,后面的参数是item;target必须先用add_library()创建过;

target_link_libraries(

native-lib

${log-lib} )

  


//头文件 .h

  


/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

#include <android/log.h> //日志头文件

/* Header for class com_hzbank_aaronkotlin_JniHelper */

  


//定义输出例子 tag = JNI_LOG

#define TAG "JNI_LOG"

#define AndroidLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)

  


#ifndef _Included_com_hzbank_aaronkotlin_JniHelper

#define _Included_com_hzbank_aaronkotlin_JniHelper

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: com_hzbank_aaronkotlin_JniHelper

* Method: getSecretKey

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_hzbank_aaronkotlin_JniHelper_getSecretKey

(JNIEnv *, jobject);

  


#ifdef __cplusplus

}

#endif

#endif

  


//cpp文件

  


//

// Created by Aaron on 2024/9/20.

//

  


#include "native-lib.h"

  


extern "C"

JNIEXPORT jstring JNICALL Java_com_hzbank_aaronkotlin_JniHelper_getSecretKey

(JNIEnv *env, jobject ){

  


//测试代码, 没有任何意义

char* app_key = "5465465416948";

  


AndroidLOGD("执行getSecretKey获取密钥");

  


//生成 Java 中的字符串对象

//指针的指针

// env <=> JNINativeInterface** C语言

return env->NewStringUTF(app_key);

  


}

  
  


//jni 基本类型

jboolean boolean 布尔值

jbyte byte 8位字符

jchar char 16位字符

jshort short 2 字节

jint int 4字节

jlong long 8字节

jfloat float 4字节

jdouble double 8

void void

jobject Object java 对象

jclass class Class 对象

jstring String 字符串

jobjectArray Object[]

jbooleanArray boolean[]

jbyteArray char[]

jshortArray short[]

jintArray int[]

jlongArray long[]

jdoubleArray double[]

jthrowable Throwable

  


JNIEnv

JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储TLS中。因此,不同的线程的JNIEnv是不同,也不能相互共享使用。JNIEnv结构也是一个函数表,在本地代码中通过JNIEnv的函数表来操作Java数据或者调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码。

  


调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码

操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象

  


JNIEnv的创建

:_JavaVM是C++中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体;

  


JNIEnv的释放

调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv

  


JNIEnv与线程

  


JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv,即线程A不能调用线程B的JNIEnv。所以JNIEnv不能跨线程。

  


JNIEnv只在当前线程有效:JNIEnv仅仅在当前线程有效,JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方便,传入的JNIEnv是同样的

本地方法匹配多个JNIEnv:在Java层定义的本地方法,能够在不同的线程调用,因此能够接受不同的JNIEnv

  


JNIEnv相关的常用函数

  


创建Java中的对象

env->NewObject()

  


jclass myClass = env->FindClass("com/example/MyClass");

jmethodID constructor = env->GetMethodID(myClass, "<init>", "(I)V");

jobject newObject = env->NewObject(myClass, constructor, value);

  


env->NewObjectA()

env->NewObjectV()

  


创建Java类中的String对象

env->NewString()

env->NewStringUTF()

  


JNI的引用

Java内存管理这块是完全透明的,new一个实例时,只知道创建这个类的实例后,会返回这个实例的一个引用,然后拿着这个引用去访问它的成员(属性、方法),完全不用管JVM内部是怎么实现的,如何为新建的对象申请内存,使用完之后如何释放内存,只需要知道有个垃圾回收器在处理这些事情就行了,然而,从Java虚拟机创建的对象传到C/C++代码就会产生引用,根据Java的垃圾回收机制,只要有引用存在就不会触发该该引用所指向Java对象的垃圾回收。

  


局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)

  


局部引用,也成本地引用,通常是在函数中创建并使用.在函数返回的时候自动释放(freed),也可以使用DeleteLocalRef函数手动释放该应用

  


全局引用可以跨方法、跨线程使用,直到被开发者显式释放。类似局部引用,一个全局引用在被释放前保证引用对象不被GC回收。和局部应用不同的是,没有俺么多函数能够创建全局引用。能创建全部引用的函数只有NewGlobalRef,而释放它需要使用ReleaseGlobalRef函数

  


弱全局引用(Weak Global Reference)

通过使用NewWeakGlobalRef、ReleaseWeakGlobalRef来产生和解除引用。

  


//静态注册native函数

  


try{

  


System.loadLibrary("native-lib")

  


}catch (e: Exception){

e.printStackTrace()

}

  


//动态注册native函数

让Java层的native方法和任意JNI函数连接起来

try{

  


System.loadLibrary("native-lib")

  


}catch (e: Exception){

e.printStackTrace()

}

  


System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个JNI_OnLoad函数兵调用该函数,这个函数的作用是告诉Dalvik虚拟机此C库使用的是哪一个JNI版本

会调用JNI_OnUnload()函数来进行善后清除工作。

  


jint JNI_OnLoad(JavaVM* vm, void* reserved)

jniRegisterNativeMethods

  
  
  


JNI规范定义的函数签名信息

Z boolean

B byte

C char

S short

I int

J long

F float

D double

[i int[]

[Ljava/lang/Object String[]

Ljava/lang/String String

Ljava/lang/Object Object

  


如果返回值是void,对应的签名是V。

  
  


native代码反调用Java层代码

  
  


jclass myClass = env->FindClass("com/example/MyClass");

jmethodID constructor = env->GetMethodID(myClass, "<init>", "(I)V");

jobject newObject = env->NewObject(myClass, constructor, value);