NDK开发基础

NDK开发基础

目录

什么是NDK

NDK(Native Development Kit):原生开发套件,是一套工具,作用是能够在安卓应用中使用C和C++代码,并提供众多的平台库,可使用这些平台库管理原生activity和访问实体设备组件。

如需为您的应用编译和调试原生代码,您需要以下组件:

  • Android 原生开发套件 (NDK):这套工具使您能在 Android 应用中使用 C 和 C++ 代码。
  • CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。
  • LLDB:Android Studio 用于调试原生代码的调试程序。

为什么需要NDK

  • 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。
  • 重复使用您自己或其他开发者的 C 或 C++ 库。

什么是JNI

Java 原生接口 (JNI):JNI 是 Java 和 C++ 组件用于相互通信的接口。

第一个NDK工程

SDK Manager中选在NDK和Cmake进行安装

image-20240715094213394

file —> New Project —> Natie C++ —> Next

image-20240715094327249

根据实际需求填写对应的信息:工程名字、包名、保存路径、开发语言、兼容的最小sdk

image-20240715094402721

选在C++版本后点击Finish

image-20240715094413847

一个默认的NDK工程创建好后,主要包括以下几个部分:

build.gradle中和NDK相关:

build.gradle文件用于配置Android Studio项目的构建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/ /这里指定了用于构建Android应用程序的Gradle插件。 
plugins {
id 'com.android.application'
}

//这一部分是用于配置Android构建选项的。

android {
namespace 'com.example.projectname' //这是项目的命名空间。
compileSdk 33 //使用版本为33的Android SDK进行编译。

defaultConfig {
//applicationId: 应用的唯一标识符。
//minSdk: 应用支持的最低SDK版本。
//targetSdk: 应用针对的SDK版本。
applicationId "com.example.projectname"
minSdk 16
targetSdk 33
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags '-std=c++11' // 用于配置CMake的本地(Native)构建。这里指定了使用C++11标准。
}
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //ndk块内的abiFilters用于指定Android应用支持的目标架构(ABI,Application Binary Interface)。指定这些ABI意味着应用将为这些架构编译相应的本地(native)代码。
}

}
}

buildTypes { //指定发布(Release)构建的配置。
release {
minifyEnabled false //是否压缩代码
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' //ProGuard规则文件。
}
}
compileOptions { //指定Java版本。
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild { //配置CMake的版本和路径。
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
buildFeatures {
viewBinding true
}
}

dependencies {
/* 声明项目的依赖库。

implementation: 应用依赖。
testImplementation: 单元测试依赖。
androidTestImplementation: Android测试依赖。
每一行都是指定一个库及其版本,这些库会在构建过程中被下载和集成。 */

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

CMakeLists:可以在这个文件中指定编译后的so库的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 设置该CMake文件所需的最低版本,这里是3.22.1
cmake_minimum_required(VERSION 3.22.1)

# 定义一个CMake项目,并给它起一个名字"projectname"
project("projectname")

# 创建一个名为"projectname"的共享库(SHARED),源代码是native-lib.cpp
# SHARED表示这是一个动态链接库,也就是.so文件(Shared Object)
add_library(
projectname # 库的名称
SHARED # 库的类型:SHARED(动态库)、STATIC(静态库)或者MODULE(模块库)
native-lib.cpp # 源文件
)

# find_library用于在系统中查找一个库,并将其路径存储在变量中
# 这里查找的是Android系统的日志库(liblog.so),并将其路径存储在log-lib变量中
find_library(
log-lib # 变量名称
log # 要查找的库名称
)

# target_link_libraries用于链接库到目标,这里把上面找到的日志库链接到我们自己的"projectname"库中
# 这样在native-lib.cpp中就可以使用Android的日志功能
target_link_libraries(
projectname # 我们自己的库
${log-lib} # Android系统的日志库
)

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include <jni.h>
#include <string>

//使用extern "C"可以确保函数名在编译后不会被C++编译器改变(即,不会发生名字改编或name mangling),从而使得Java能够准确地找到这个函数。
//Java_com_example_projectname_MainActivity_stringFromJNI这个函数名有特定的格式:Java_包名_类名_方法名。这样命名是为了让JNI知道这个函数对应Java中哪一个类和方法。
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_projectname_MainActivity_stringFromJNI(
JNIEnv* env, //指向JNIEnv结构体的指针,该结构体包含了大量用于JNI编程的函数
jobject /* this */) {//这是调用该函数的Java对象。
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str()); //通过JNIEnv指针调用NewStringUTF方法,将C++字符串转换成Java字符串(jstring)
}

要在java中调用上面的c++方法,需要在对应的java代码中加载对应的so,以及声明native方法

1
2
3
4
5
6
public native String stringFromJNI();

// Used to load the 'projectname' library on application startup.
static {
System.loadLibrary("projectname");
}

so中的log输出

1
2
3
4
5
6
7
#include <android/log.h>

#define TAG "sectest"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);

image-20240715094435321

image-20240715094444041

#define中的…表示接受可变参数,传入的可变参数替换到对应的 __VA_ARGS__处,用法如上。

NDK多线程pthread

头文件引入

1
2
#include <pthread.h>

线程函数

定义一个线程将要执行的函数。

1
2
3
4
5
void* myThread(void* arg) {
// 线程执行的代码
return nullptr;
}

创建和启动线程

1
2
3
pthread_t thread;
int result = pthread_create(&thread, NULL, myThread, NULL);

不需要手动给 thread 赋值。它的值会在 pthread_create 成功创建新线程后被自动设置。这个值通常会被用于其他 pthread 函数,如 pthread_join(thread, NULL);,以便进行后续的线程管理。

所以,虽然在声明 pthread_t thread; 时没有显式地进行初始化或赋值,但它会在 pthread_create 调用成功后包含新线程的标识符。这个标识符是由系统自动生成和管理的。

等待线程结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pthread_join(thread, NULL);
#include <pthread.h>
#include <android/log.h>

void* myThread(void* arg) {
__android_log_print(ANDROID_LOG_INFO, "MyApp", "Hello from pthread");
return nullptr;
}

extern "C" JNIEXPORT void JNICALL Java_com_example_myapp_MainActivity_startThread(JNIEnv*, jobject) {
pthread_t thread;
pthread_create(&thread, NULL, myThread, NULL);
pthread_join(thread, NULL);
}

JNI_OnLoad

JNI_OnLoad 是 JNI (Java Native Interface) 中的一个特殊函数,它会在 native 库被加载时执行。如果你在 native 库中定义了 JNI_OnLoad,当 Java 代码使用 System.loadLibraryRuntime.loadLibrary 加载这个库时,JNI_OnLoad 会被调用。

此函数为开发者提供了一个机会在库被加载时执行一些初始化操作,比如注册 native 方法、初始化 native 数据结构等。

1
2
3
4
5
6
7
8
9
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("GetEnv failed");
return -1;
}
return JNI_VERSION_1_6;
}

一个so中可以不定义JNI_OnLoad

一旦定义了JNI_OnLoad,在so被加载的时候会自动执行

必须返回JNI版本JNI_VERSION_1_6


  1. JavaVM* vm:是一个指向 Java 虚拟机的指针,可以使用它来获取 JNIEnv,这是与 JVM 进行交互的主要工具。
  2. void* reserved:是一个预留的参数。目前,通常不需要担心这个参数。
  3. 函数的返回值是native 代码所支持的 JNI 版本。在上面的例子中,我们返回了 JNI_VERSION_1_6,表示我们的 native 代码是基于 JNI 1.6 的。

JNIEnv

JNIEnv:java native interface 。这是一个指向结构体的指针,这个结构体包含了大量的函数指针,这些函数提供了jni的大部分功能。

JNIEnv为native层提供了与java虚拟机交互的能力,使得本地代码能够操作java对象、调用java方法、捕获java异常等。

JNIEnv指针的获取或使用场景

1)静态注册调用

当java调用静态注册的native方法时,JVM会自动将JNIEnv指针作为第一个参数传递给本地方法

native方法:

1
2
3
4
5
6
JNIEXPORT void JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(JNIEnv *env, jobject obj) {
// 在这里,env就是JNIEnv指针,可以直接使用
jclass clazz = env->FindClass("java/lang/String");
}

java层调用:

image-20240715094630867

2)在JNI_OnLoad中获取

当so库被加载时,JNI_OnLoad函数会被自动调用,其中也可以获取JNIEnv指针:

1
2
3
4
5
6
7
8
9
10
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI版本不匹配
}
// 在这里,env就是JNIEnv指针,可以使用了
return JNI_VERSION_1_6;
}

3) 在子线程中获取

先在JNI_OnLoad中获取JavaVM指针保存在一个全局变量中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//定义一个全局变量接收JavaVM指针变量
JavaVM* globalVM;

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGD("this is form JNI_OnLoad");

JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("GetEnv failed");
return -1;
}

LOGD("JavaVM %p", vm);
//GetJavaVM函数会将获取到的JavaVM指针存储在这个地址指向的变量中。通常,开发者会将这个JavaVM指针保存为全局变量,以便在其他地方使用。
env->GetJavaVM(&globalVM);
LOGD("JavaVM %p", globalVM);
LOGD("JNI_OnLoad JNIEnv %p", env);


return JNI_VERSION_1_6;
}

再在子线程中通过JavaVM指针获取JNIEnv指针

1
2
3
4
5
6
7
8
9
10
11
void myThread(){

JNIEnv *env = nullptr;
if (globalVM->AttachCurrentThread((JNIEnv **) &env, nullptr) != JNI_OK) {
LOGD("GetEnv failed");
}
LOGD("myThread JNIEnv %p", env);


LOGD("this is from myThread");
}

JNI函数静态注册

在静态注册中,C/C++函数的名称需要遵循一定的命名规则,格式为Java_包名_类名_方法名

系统会通过dlopen加载对应的so,通过dlsym来获取指定名字的函数地址,然后调用 静态注册的jni函数,必然在导出表里

1
2
3
4
5
6
7
8
public class NativeClass {
static {
System.loadLibrary("native-lib");
}

public native String nativeMethod();
}

C/C++代码示例:

1
2
3
4
5
6
7
8
#include <jni.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_NativeClass_nativeMethod(JNIEnv *env, jobject obj) {
return env->NewStringUTF("Hello from JNI!");
}


JNI函数动态注册

通过env→RegisterNatives注册函数,通常在JNI_OnLoad中注册JNINativeMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <jni.h>

static jstring nativeMethod(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello from JNI!");
}

static JNINativeMethod methods[] = {
{"nativeMethod", "()Ljava/lang/String;", (void *)nativeMethod},
};

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

jclass clazz = (*env)->FindClass(env, "com/example/NativeClass");
if (clazz == NULL) {
return JNI_ERR;
}

if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

下面是对这段代码中使用的各种方法及参数的详细解释:

1. JNI_OnLoad

1
2
3
4
5
6
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
// ...
return JNI_VERSION_1_6;
}

  • JNI_OnLoad是JNI库加载时被调用的函数,通常在这个函数中进行动态注册。
  • JavaVM *vm:这是Java虚拟机的指针,可以用于获取JNIEnv指针或者在其他线程中使用。
  • 返回值:该函数返回你的JNI库所需的JNI版本号,通常返回JNI_VERSION_1_6

2. GetEnv

1
2
3
4
5
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

  • GetEnv函数用于获取与当前线程关联的JNIEnv指针。
  • JNIEnv **env:用于存储获取到的JNIEnv指针的地址。
  • JNI_VERSION_1_6:表示所需的JNI版本号。
  • 如果函数返回JNI_OK,则获取JNIEnv指针成功,否则返回JNI_ERR

3. FindClass

1
2
3
4
5
jclass clazz = (*env)->FindClass(env, "com/example/NativeClass");
if (clazz == NULL) {
return JNI_ERR;
}

  • FindClass函数用于查找Java类。
  • "com/example/NativeClass":要查找的Java类的完全限定名。
  • 如果找到了类,则返回类的引用;如果没找到,则返回NULL。

4. RegisterNatives

1
2
3
4
5
6
7
8
9
10
11
12
static jstring nativeMethod(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello from JNI!");
}

static JNINativeMethod methods[] = {
{"nativeMethod", "()Ljava/lang/String;", (void *)nativeMethod},
};

if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}

  • RegisterNatives函数用于注册本地方法。
  • jclass clazz:要注册本地方法的Java类的引用。
  • JNINativeMethod methods[]:一个数组,包含要注册的本地方法的信息。每个JNINativeMethod结构体包含三个字段:Java方法名、方法签名和C/C++函数指针。
  • sizeof(methods) / sizeof(methods[0]):计算数组中的元素个数,即要注册的本地方法的个数。
  • 如果注册成功,则函数返回0;如果注册失败,则返回一个负数。

5. NewStringUTF

1
2
return (*env)->NewStringUTF(env, "Hello from JNI!");

  • NewStringUTF函数用于创建一个新的Java字符串对象。
  • "Hello from JNI!":C字符串,表示要创建的Java字符串的内容。

这段代码的整体流程是:在JNI_OnLoad函数中获取JNIEnv指针,查找Java类,然后使用RegisterNatives函数注册本地方法。如果在任何步骤中出现错误,函数会返回JNI_ERR。如果一切正常,则返回所需的JNI版本号。

so路径的动态获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String getPath(Context cxt){
PackageManager pm = cxt.getPackageManager();
List<PackageInfo> pkgList = pm.getInstalledPackages(0);
if (pkgList == null || pkgList.size() == 0) return null;
for (PackageInfo pi : pkgList) {
if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/")
&& pi.packageName.startsWith("com.xiaojianbang.demo")) {
//Log.e("xiaojianbang", pi.applicationInfo.nativeLibraryDir);
return pi.applicationInfo.nativeLibraryDir;
}
}
return null;
}

多个cpp文件编译成一个so

CMakeLists.txt配置文件中的add_library添加多个cpp文件名

image-20240715094710924

image-20240715094718827

image-20240715094730129

代码运行结果:

image-20240715094738956

so编译结果

image-20240715094747183

so反编译结果

image-20240715094756903

编译多个so

编写多个cpp文件

image-20240715094804688

image-20240715094813624

修改CMakeLists.txt

image-20240715094821230

image-20240715094827933

Java静态代码块加载多个so

image-20240715094837585

编译结果

image-20240715094844717

so之间的相互调用

使用dlopen、dlsym、dlclose获取函数地址,然后调用。需要导入dlfcn.h

extern 函数声明要调用的方法,避免C++命名修饰的影响。

image-20240715094854186

image-20240715094900929

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <jni.h>
#include <string>
#include <dlfcn.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_sotest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
char* hello = "Hello from C++";

void* handle = dlopen("libtestcppso.so", RTLD_LAZY);

if (handle) {
// 使用dlsym查找函数地址
void (*runtestcpp)(char*) = reinterpret_cast<void (*)(char *)> (dlsym(handle, "runtestcpp"));
if (runtestcpp) {
// 调用函数
runtestcpp(hello);
}
// 使用dlclose关闭so文件
dlclose(handle);
}

return env->NewStringUTF(hello);
}

运行结果:

image-20240715094911944

JNI创建java对象

使用NewObject创建对象

➡️ 功能:NewObject不仅分配对象内存,还调用对象的构造函数。

➡️ 用法:

1
2
3
4

jobject NewObject( jclass clazz, jmethodID methodID, ...);


➡️** 参数:**

clazz:要创建的Java类的引用。
methodID:构造函数的ID。
…:构造函数的参数。

➡️ 返回值:

返回一个新创建的Java对象。

➡️ C/C++代码示例:

image-20240715094930711

1
2
3
4
5
6
//通过jni创建java对象
jclass clazz = env ->FindClass("com/example/sotest/NdkDemo");
jmethodID methdID = env ->GetMethodID(clazz,"<init>", "()V");
//使用NewObject创建对象
jobject javademoObj = env ->NewObject(clazz,methdID);
LOGD("通过jni创建java对象success");

用于创建对象的java类

image-20240715094940804

1
2
3
4
5
6
7
8
9
package com.example.sotest;

import android.util.Log;

public class NdkDemo {
public NdkDemo(){
Log.d("wnxwzr", "NdkDemo() running !Call by native code");
}
}

➡️ 代码运行结果:

image-20240715094950586

使用AllocObject创建对象

👉🏿 功能:AllocObject只分配对象内存,但不调用构造函数。

👉🏿 用法:

1
2
3
4

jobject AllocObject(jclass clazz);


👉🏿 参数:

clazz:要创建的Java类的引用。

👉🏿 返回值:

返回一个未初始化的Java对象。
需要手动初始化对象或避免调用构造函数的情况

1
2
3
4
5
6
7
8
9
10
11
jclass clazz = env ->FindClass("com/example/sotest/NdkDemo");

jmethodID methdID2 = env ->GetMethodID(clazz,"<init>", "(Ljava/lang/String;)V");
jobject javademoObj2 = env->AllocObject(clazz);// 仅分配对象内存,不调用构造函数
// 需要手动调用初始化方法
jstring jstr = env->NewStringUTF("from jni str CallNonvirtualVoidMethod");
//CallNonvirtualVoidMethod用于调用构造函数
env->CallNonvirtualVoidMethod(javademoObj2, clazz, methdID2, jstr);
//不同的返回值 调用相应的CallxxMethod方法,如下所示


image-20240715095002734

1
2
3
4
5
6
7
8
9
10
public class NdkDemo {
public NdkDemo(){
Log.d("wnxwzr", "NdkDemo() running !Call by native code");
}

public NdkDemo(String who){
Log.d("wnxwzr", "NdkDemo(String) running !Call by native code"+who);
}
}

JNI访问java属性

获取静态字段

1
2
3
4
5
6
7
jclass clazz = env ->FindClass("com/wnxwzr/sotest1/MainActivity");
jfieldID privateStaticStringField = env->GetStaticFieldID(clazz, "privateStaticStringField", "Ljava/lang/String;");
jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(clazz, privateStaticStringField));
const char* privatecstr = env->GetStringUTFChars(privateStaticString, nullptr);
LOGD("privateStaticString: %s", privatecstr);
env->ReleaseStringUTFChars(privateStaticString, privatecstr);

获取对象字段

ReflectDemoObj对象可以在native中本地创建,也可以通过从java层获取对象传递过来。

1
2
3
4
5
6
7

jfieldID publicStringField = env->GetFieldID(clazz, "publicStringField", "Ljava/lang/String;");
jstring publicString = static_cast<jstring>(env->GetObjectField(ReflectDemoObj, publicStringField));
const char* publiccstr = env->GetStringUTFChars(publicString, nullptr);
LOGD("publicStringField: %s", publiccstr);
env->ReleaseStringUTFChars(publicString, publiccstr);

设置字段

1
2
env->SetObjectField(ndkobj, privateStringFieldID, env->NewStringUTF("xxxxx"));

通过JNI访问java数组

获取数组字段id

1
2
3
4
5
jfieldID byteArrayID = env->GetFieldID(clazz, "byteArray", "[B");
jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
int _byteArrayLength = env->GetArrayLength(byteArray);


修改数组字段

1
2
3
4
5
6
7
char javaByte[10];
for(int i = 0; i < 10; i++){
javaByte[i] = static_cast<char>(100 - i);
}
const jbyte *java_array = reinterpret_cast<const jbyte *>(javaByte);
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength, java_array);

获取数组字段

1
2
3
4
5
6
7
8
byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
_byteArrayLength = env->GetArrayLength(byteArray);
char* str = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
for(int i = 0; i< _byteArrayLength; i++){
LOGD("str[%d]=%d", i, str[i]);
}
env->ReleaseByteArrayElements(jbyteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);

通过JNI访问java方法

调用静态函数

1
2
3
4
jclass ReflectDemoClazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID publicStaticFuncID = env->GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc", "()V");
env->CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);

调用对象函数

1
2
3
4
5
6
7
jmethodID privateFuncID = env->GetMethodID(ReflectDemoClazz, "privateFunc", "(Ljava/lang/String;I)Ljava/lang/String;");
jmethodID ReflectDemoInit = env->GetMethodID(ReflectDemoClazz, "<init>", "(Ljava/lang/String;)V");
jstring str1 = env->NewStringUTF("this is from NDK");
jobject ReflectDemoObj = env->NewObject(ReflectDemoClazz, ReflectDemoInit, str1);
jstring str2 = env->NewStringUTF("this is from JNI");
jstring retval = static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj, privateFuncID, str2, 1000));

CallObjectMethodA的使用

1
2
3
4
5
6
7
8
jvalue args[2];
args[0].l = str2;
args[1].i = 1000;
jstring retval = static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);

参数说明:

  • env: 指向JNI环境的指针,用于访问JNI接口。
  • obj: 要调用方法的Java对象。
  • methodID: 要调用的方法的ID,可以通过GetMethodIDGetStaticMethodID获取。
  • args: 指向一个jvalue数组的指针,该数组包含要传递给方法的参数。

返回值:

  • 返回调用的Java方法的结果,如果方法的返回类型是对象,则返回一个jobject;如果方法是void类型,则返回NULL。

CallObjectMethodACallObjectMethod的区别在于,CallObjectMethodA允许您通过jvalue数组传递参数,这在处理可变参数列表时特别有用。

参数是数组,返回值是数组的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
jclass StringClazz = env->FindClass("java/lang/String");
jobjectArray StringArr = env->NewObjectArray(3, StringClazz, nullptr);
for(int i = 0; i < 3; i++){
jstring str3 = env->NewStringUTF("NDK");
env->SetObjectArrayElement(StringArr, i, str3);
//env->DeleteLocalRef(str3);
}
jmethodID privateStaticFuncID = env->GetStaticMethodID(ReflectDemoClazz, "privateStaticFunc", "([Ljava/lang/String;)[I");
jintArray intArr = static_cast<jintArray>(env->CallStaticObjectMethod(ReflectDemoClazz, privateStaticFuncID, StringArr));
int *cintArr = env->GetIntArrayElements(intArr, nullptr);
LOGD("cintArr[0]=%d", cintArr[0]);
env->ReleaseIntArrayElements(intArr, cintArr, JNI_ABORT);

通过JNI访问java父类

1
2
3
4
5
//super.onCreate(savedInstanceState);
jclass AppCompatActivityClazz = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivityClazz, onCreateID, saved_instance_state);