1. Demo 概述
这个 Demo 演示了 Android JNI 开发中最常用的几类交互场景,分为两个文件:
- Java 层 :
Test.java(包名com.qhsoft.jnitestdemo)------ 声明 native 方法、提供供 C 回调的方法 - Native 层 :
test.cpp------ 各 native 方法的 C/C++ 实现
覆盖的核心场景:
| 场景 | 对应方法 |
|---|---|
| Java 调 C · 返回字符串 | showMsg |
| Java 调 C · 基本类型运算 | sum |
| Java 调 C · 字符串拼接 | append |
| C 回调 Java 方法 | callC |
| 遍历 Java 集合(ArrayList) | sumList |
| 遍历对象集合并构造新对象 | sumAge |
| 字符串 / 数组操作 | length、sumInt、getByteArray |
| 在 C 中创建 Java 对象 | getPerson |
注意:Demo 中用到了
com.qhsoft.jnitestdemo.Person类(含name、age字段及对应 getter/setter),该类不在此文档中。
2. Java 层:Test.java
要点:
static {}代码块中通过System.loadLibrary("TestDemo")加载 Native 库libTestDemo.so,类加载时执行一次- 所有
native方法只有声明、没有实现,具体逻辑全部在 C 层完成 showJava是一个普通 Java 方法,专门用来被 C 层回调(弹 Toast)
java
package com.qhsoft.jnitestdemo;
import android.content.Context;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class Test {
// 加载 Native 库(libTestDemo.so)
static {
System.loadLibrary("TestDemo");
}
private Context mContext;
public Test(Context mContext) {
this.mContext = mContext;
}
// 供 C 层回调的 Java 方法:弹一个 Toast
public void showJava(String str) {
Toast.makeText(mContext, str, Toast.LENGTH_LONG).show();
}
// ============ 声明的 native 方法(实现见 test.cpp) ============
public native String showMsg(); // 返回一个字符串
public native void callC(String str); // C 层回调 showJava
public native float sum(float i, float j); // 两个 float 求和
public native String append(String a, String b); // 拼接两个字符串
public native int sumList(ArrayList<Integer> list); // 遍历 Integer 列表求和
public native Person sumAge(List<Person> list); // 汇总所有人年龄,返回新 Person
public native int length(String str); // 获取字符串长度
public native int sumInt(int[] src); // int 数组求和
public native byte[] getByteArray(byte[] array); // byte 数组每个元素 +10
public native Person getPerson(); // 在 C 中构造 Person 对象
}
3. Native 层:test.cpp
3.1 头文件与日志宏
- 引入 JNI 头文件与 Android 日志头文件
LOG_D/LOG_E封装了__android_log_print,可在 Logcat 中用 Tagjni过滤输出
arduino
#include <jni.h>
#include <cstring>
#include <malloc.h>
#include <android/log.h>
#include <__locale>
using namespace std;
#define LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG, "jni", __VA_ARGS__)
#define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, "jni", __VA_ARGS__)
extern "C" {
说明:所有函数都包在
extern "C"中,避免 C++ 名称修饰导致 JNI 找不到符号;文档结尾应有对应的}。
3.2 返回字符串:showMsg
最简单的 JNI 用法:直接用 NewStringUTF 把 C 字符串转成 jstring 返回给 Java。
bash
JNIEXPORT jstring JNICALL
Java_com_qhsoft_jnitestdemo_Test_showMsg(JNIEnv *env, jobject obj) {
return env->NewStringUTF("欢迎世界");
}
3.3 C 回调 Java 方法:callC
演示 C 层主动调用 Java 方法 的完整三步:
FindClass拿到 Java 类(等价写法:GetObjectClass(obj))GetMethodID拿到方法 ID ------ 注意签名(Ljava/lang/String;)V表示「参数 String、返回 void」CallVoidMethod真正调用
ini
JNIEXPORT void JNICALL Java_com_qhsoft_jnitestdemo_Test_callC
(JNIEnv *env, jobject obj, jstring jstr) {
jclass myclass = env->FindClass("com/qhsoft/jnitestdemo/Test");
// jclass myclass = env->GetObjectClass(obj); // 等价写法
jmethodID mid = env->GetMethodID(myclass, "showJava", "(Ljava/lang/String;)V");
env->CallVoidMethod(obj, mid, jstr);
}
3.4 基本类型运算:sum
基本类型(jint / jfloat 等)可以直接当 C 类型用,无需转换。
arduino
JNIEXPORT jfloat JNICALL
Java_com_qhsoft_jnitestdemo_Test_sum(JNIEnv *env, jobject obj, jfloat i, jfloat j) {
return i + j;
}
3.5 字符串拼接:append
GetStringUTFChars把jstring转成 C 的char*GetStringLength拿到字符长度,据此malloc结果缓冲区strcpy+strcat完成拼接,再包回jstring返回
scss
JNIEXPORT jstring JNICALL
Java_com_qhsoft_jnitestdemo_Test_append(JNIEnv *env, jobject obj, jstring a, jstring b) {
const char* cstr1 = env->GetStringUTFChars(a, JNI_FALSE);
int alen = env->GetStringLength(a);
char* rtn = (char *) malloc(alen + 1); // "\0" 申请空间
strcpy(rtn, cstr1);
const char* cstr2 = env->GetStringUTFChars(b, JNI_FALSE);
strcat(rtn, cstr2);
return env->NewStringUTF(rtn);
}
3.6 遍历 ArrayList 求和:sumList
Java 的 ArrayList 在 C 里就是一个 jobject,操作方式是反射式调用:
GetObjectClass拿到 List 类,再取size()和get()两个方法 ID- 循环内
CallObjectMethod逐个取出元素(得到Integer对象) - 对每个元素再反射调用
intValue()拆箱累加
ini
JNIEXPORT jint JNICALL
Java_com_qhsoft_jnitestdemo_Test_sumList(JNIEnv* env, jobject obj, jobject objectList) {
jclass class_list = env->GetObjectClass(objectList);
jmethodID jmethodId_size = env->GetMethodID(class_list, "size", "()I");
jmethodID jmethodId_get = env->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
int len = env->CallIntMethod(objectList, jmethodId_size);
LOG_D("len:%d", len);
int sum = 0;
for (int i = 0; i < len; i++) {
jobject jobject1 = env->CallObjectMethod(objectList, jmethodId_get, i);
jclass jclass1 = env->GetObjectClass(jobject1);
jmethodID intvalue_Id = env->GetMethodID(jclass1, "intValue", "()I");
int x = env->CallIntMethod(jobject1, intvalue_Id);
LOG_D("get获取到的值:%d", x);
sum = sum + x;
}
return sum;
}
3.7 汇总对象年龄并返回新对象:sumAge
在 3.6 的基础上更进一步:
- 遍历
List<Person>,反射调用每个元素的getAge()累加 - 通过
FindClass("com/qhsoft/jnitestdemo/Person")+NewObject在 C 中创建新 Person - 用
GetFieldID+SetIntField/SetObjectField直接给字段age、name赋值
ini
JNIEXPORT jobject JNICALL
Java_com_qhsoft_jnitestdemo_Test_sumAge(JNIEnv *env, jobject obj, jobject objectList) {
jclass class_list = env->GetObjectClass(objectList);
jmethodID jmethodId_size = env->GetMethodID(class_list, "size", "()I");
jmethodID jmethodId_get = env->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
int len = env->CallIntMethod(objectList, jmethodId_size);
LOG_D("len:%d", len);
int sumAge = 0;
for (int i = 0; i < len; i++) {
jobject jobject1 = env->CallObjectMethod(objectList, jmethodId_get, i);
jclass jclass1 = env->GetObjectClass(jobject1);
jmethodID getAge_Id = env->GetMethodID(jclass1, "getAge", "()I");
int x = env->CallIntMethod(jobject1, getAge_Id);
sumAge = sumAge + x;
}
// 创建结果对象:new Person(),并设置 age / name 字段
jclass jclass2 = env->FindClass("com/qhsoft/jnitestdemo/Person");
jmethodID jmethodId = env->GetMethodID(jclass2, "<init>", "()V");
jobject jobject2 = env->NewObject(jclass2, jmethodId);
jfieldID jfieldId = env->GetFieldID(jclass2, "age", "I");
env->SetIntField(jobject2, jfieldId, sumAge);
jfieldID jfieldId1 = env->GetFieldID(jclass2, "name", "Ljava/lang/String;");
jstring name = env->NewStringUTF("hai");
env->SetObjectField(jobject2, jfieldId1, name);
return jobject2;
}
3.8 字符串长度:length
rust
JNIEXPORT jint JNICALL
Java_com_qhsoft_jnitestdemo_Test_length(JNIEnv *env, jobject obj, jstring str) {
return env->GetStringLength(str);
}
3.9 int 数组求和:sumInt
GetArrayLength取数组长度GetIntArrayElements把 Java 数组转成 C 指针(获取的是数组副本或直接引用)
ini
JNIEXPORT jint JNICALL
Java_com_qhsoft_jnitestdemo_Test_sumInt(JNIEnv *env, jobject obj, jintArray str) {
int len = env->GetArrayLength(str);
int* result = env->GetIntArrayElements(str, JNI_FALSE);
int sum = 0;
for (int i = 0; i < len; i++) {
sum = sum + result[i];
}
return sum;
}
3.10 byte 数组处理:getByteArray
演示 JNI 数组操作的标准四步流程:取指针 → 处理 → 释放 → 回写。
ini
JNIEXPORT jbyteArray JNICALL
Java_com_qhsoft_jnitestdemo_Test_getByteArray(JNIEnv *env, jobject obj, jbyteArray j_array) {
// 1. 获取数组指针和长度
jbyte *c_array = env->GetByteArrayElements(j_array, 0);
int len_arr = env->GetArrayLength(j_array);
// 2. 具体处理:每个元素 +10
jbyteArray c_result = env->NewByteArray(len_arr);
jbyte buf[len_arr];
for (int i = 0; i < len_arr; i++) {
buf[i] = c_array[i] + 10;
}
// 3. 释放内存(必须调用,否则内存泄漏)
env->ReleaseByteArrayElements(j_array, c_array, 0);
// 4. 把结果写回新数组并返回
env->SetByteArrayRegion(c_result, 0, len_arr, buf);
return c_result;
}
3.11 在 C 中构造对象:getPerson
与 3.7 直接给字段赋值不同,这里反射调用的是 setter 方法 (setAge / setName),并用 DeleteLocalRef 主动释放局部引用。
ini
JNIEXPORT jobject JNICALL
Java_com_qhsoft_jnitestdemo_Test_getPerson(JNIEnv *env, jobject obj) {
jclass clazz = env->FindClass("com/qhsoft/jnitestdemo/Person");
jmethodID mid_construct = env->GetMethodID(clazz, "<init>", "()V");
jobject jobj = env->NewObject(clazz, mid_construct);
jmethodID age_id = env->GetMethodID(clazz, "setAge", "(I)V");
env->CallVoidMethod(jobj, age_id, 30);
jmethodID name_id = env->GetMethodID(clazz, "setName", "(Ljava/lang/String;)V");
jstring jstring1 = env->NewStringUTF("hai");
env->CallVoidMethod(jobj, name_id, jstring1);
env->DeleteLocalRef(clazz);
return jobj;
}
4. 附:JNI 方法命名与常用 API 速查
方法命名规则 :Java_完整类名(点换下划线)_方法名,如 Java_com_qhsoft_jnitestdemo_Test_showMsg。
本文用到的 JNI API:
| API | 用途 |
|---|---|
NewStringUTF |
创建 jstring 返回给 Java |
GetStringUTFChars / GetStringLength |
jstring 转 C 字符串 / 取长度 |
FindClass / GetObjectClass |
按全限定名 / 按对象拿 jclass |
GetMethodID / GetFieldID |
拿方法 / 字段 ID(需传签名) |
CallVoidMethod / CallIntMethod / CallObjectMethod |
反射调用 Java 方法 |
NewObject |
在 C 中创建 Java 对象(<init> 构造器) |
SetIntField / SetObjectField |
直接给对象字段赋值 |
GetArrayLength / GetIntArrayElements / ReleaseByteArrayElements / SetByteArrayRegion |
数组:取长度、取指针、释放、回写 |
DeleteLocalRef |
主动释放局部引用 |