我来详细讲解JNI和NDK开发的完整流程。
## 一、JNI和NDK概述
### JNI (Java Native Interface)
- Java本地接口,允许Java代码与其他语言(主要是C/C++)编写的代码进行交互
- 是Java平台的一部分,不限于Android
### NDK (Native Development Kit)
- Android原生开发工具包
- 提供了在Android上使用C/C++的工具链
- 包含编译器、调试器和构建脚本
## 二、JNI开发流程
### 1. 创建Java类并声明native方法
```java
package com.example.jnidemo;
public class NativeLib {
// 加载so库
static {
System.loadLibrary("native-lib");
}
// 声明native方法
public native String stringFromJNI();
public native int add(int a, int b);
public native void callJavaMethod();
}
```
### 2. 生成JNI头文件(可选,现在通常直接写)
```bash
# 老方法:使用javah生成头文件
javah -jni com.example.jnidemo.NativeLib
```
### 3. 实现C/C++代码
```cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C" {
// 标准命名规则:Java_包名_类名_方法名
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_NativeLib_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jint JNICALL
Java_com_example_jnidemo_NativeLib_add(
JNIEnv* env,
jobject /* this */,
jint a,
jint b) {
return a + b;
}
}
```
## 三、JNI加载和初始化流程
### 最先执行的函数:JNI_OnLoad
```cpp
// 当System.loadLibrary()被调用时,JNI_OnLoad是第一个被执行的函数
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
LOGI("JNI_OnLoad called");
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 可以在这里注册native方法(动态注册)
registerNativeMethods(env);
// 返回JNI版本
return JNI_VERSION_1_6;
}
// 对应的卸载函数
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM* vm, void* reserved) {
LOGI("JNI_OnUnload called");
}
```
## 四、Native方法注册方式
### 1. 静态注册(默认方式)
按照命名规则编写函数即可:
```cpp
// Java_包名_类名_方法名
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_NativeLib_stringFromJNI(JNIEnv* env, jobject thiz) {
return env->NewStringUTF("Hello");
}
```
### 2. 动态注册(推荐)
```cpp
// Native方法实现
static jstring nativeGetString(JNIEnv* env, jobject thiz) {
return env->NewStringUTF("Hello from dynamic registration");
}
static jint nativeAdd(JNIEnv* env, jobject thiz, jint a, jint b) {
return a + b;
}
// 方法映射表
static JNINativeMethod methods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void*)nativeGetString},
{"add", "(II)I", (void*)nativeAdd}
};
// 注册函数
static int registerNativeMethods(JNIEnv* env) {
jclass clazz = env->FindClass("com/example/jnidemo/NativeLib");
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, methods,
sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
```
## 五、JNI数据类型映射
### 基本类型映射
```cpp
// Java类型 -> JNI类型
boolean -> jboolean
byte -> jbyte
char -> jchar
short -> jshort
int -> jint
long -> jlong
float -> jfloat
double -> jdouble
void -> void
```
### 引用类型
```cpp
// 所有Java对象都是jobject
String -> jstring
Object[] -> jobjectArray
boolean[] -> jbooleanArray
byte[] -> jbyteArray
// ... 其他数组类型
Class -> jclass
Throwable -> jthrowable
```
## 六、JNI常用操作
### 1. 字符串操作
```cpp
// Java String -> C字符串
jstring jstr = ...;
const char* cstr = env->GetStringUTFChars(jstr, NULL);
// 使用cstr
env->ReleaseStringUTFChars(jstr, cstr);
// C字符串 -> Java String
const char* cstr = "Hello";
jstring jstr = env->NewStringUTF(cstr);
```
### 2. 数组操作
```cpp
// 获取数组
jintArray arr = ...;
jint* elements = env->GetIntArrayElements(arr, NULL);
jsize length = env->GetArrayLength(arr);
// 处理数组
for (int i = 0; i < length; i++) {
elements[i] = elements[i] * 2;
}
// 释放数组
env->ReleaseIntArrayElements(arr, elements, 0);
```
### 3. 调用Java方法
```cpp
JNIEXPORT void JNICALL
Java_com_example_jnidemo_NativeLib_callJavaMethod(JNIEnv* env, jobject thiz) {
// 获取类
jclass clazz = env->GetObjectClass(thiz);
// 获取方法ID
jmethodID methodId = env->GetMethodID(clazz, "callback", "(Ljava/lang/String;)V");
// 调用方法
jstring msg = env->NewStringUTF("Hello from Native");
env->CallVoidMethod(thiz, methodId, msg);
// 清理本地引用
env->DeleteLocalRef(msg);
}
```
## 七、NDK项目配置
### 1. CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.10.2)
project("jnidemo")
# 添加库
add_library(
native-lib
SHARED
native-lib.cpp
)
# 查找日志库
find_library(
log-lib
log
)
# 链接库
target_link_libraries(
native-lib
${log-lib}
)
```
### 2. build.gradle配置
```gradle
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
```
## 八、完整示例
### Java端
```java
public class MainActivity extends AppCompatActivity {
private NativeLib nativeLib;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nativeLib = new NativeLib();
// 调用native方法
String result = nativeLib.stringFromJNI();
int sum = nativeLib.add(10, 20);
// 设置回调
nativeLib.setCallback(new NativeLib.Callback() {
@Override
public void onMessage(String msg) {
Log.d("MainActivity", "Received: " + msg);
}
});
nativeLib.triggerCallback();
}
}
```
### Native端
```cpp
#include <jni.h>
#include <string>
// 保存JavaVM指针
static JavaVM* g_vm = nullptr;
static jobject g_callback = nullptr;
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
g_vm = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_example_jnidemo_NativeLib_setCallback(JNIEnv* env, jobject thiz, jobject callback) {
// 保存全局引用
if (g_callback != nullptr) {
env->DeleteGlobalRef(g_callback);
}
g_callback = env->NewGlobalRef(callback);
}
JNIEXPORT void JNICALL
Java_com_example_jnidemo_NativeLib_triggerCallback(JNIEnv* env, jobject thiz) {
if (g_callback == nullptr) return;
jclass clazz = env->GetObjectClass(g_callback);
jmethodID method = env->GetMethodID(clazz, "onMessage", "(Ljava/lang/String;)V");
jstring msg = env->NewStringUTF("Hello from Native!");
env->CallVoidMethod(g_callback, method, msg);
env->DeleteLocalRef(msg);
}
```
## 九、调试技巧
### 1. 日志输出
```cpp
#include <android/log.h>
#define LOG_TAG "NativeDebug"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
LOGD("Value: %d", value);
```
### 2. 异常处理
```cpp
// 检查异常
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
// 抛出异常
jclass exClass = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(exClass, "Invalid argument");
```
## 总结
1. **加载顺序**:System.loadLibrary() → JNI_OnLoad() → Native方法调用
2. **JNI_OnLoad**是最先执行的函数,用于初始化和动态注册
3. 推荐使用动态注册方式,更灵活且不依赖命名规则
4. 注意内存管理,及时释放本地引用
5. 合理使用全局引用保存Java对象
6. 做好异常处理,避免崩溃