Android JNI/NDK回调实现问题及代码求助
Android JNI 双向回调完整解决方案(修复你的现有代码+可运行示例)
先直接解决你现有代码里的几个核心问题,再实现你需要的两种Java/C交互模式:
一、先修复你现有代码的致命问题
1. 为什么InvokeFunction从未被调用?
你的JNI函数签名和Java侧的native声明不匹配,而且函数名不符合JNI规范:
- 正确的JNI函数名应该是
Java_包名_类名_方法名(比如你的类是org.foo.bar.MainActivity,方法是startTickThread,那函数名应该是Java_org_foo_bar_MainActivity_startTickThread) extern "C"必须包裹整个函数声明,避免C++名字 mangling- Java侧必须声明对应的
native方法,主动调用它
2. 为什么queryRuntimeInfo获取方法ID失败?
- 类名大小写错误:你写的
org/foo/bar/mainactivity,但Java类名是MainActivity,应该是org/foo/bar/MainActivity - 方法签名不匹配:比如
getBuildVersion()的签名是()Ljava/lang/String;,要确保Java侧确实有这个静态方法 - 建议用
javap -s生成准确的方法签名,避免手动写错
3. 为什么UpdateTicks从未执行?
因为InvokeFunction没被调用,线程根本没创建。另外还要注意:
- 线程创建后,
AttachCurrentThread要正确处理,避免重复Attach - 方法ID应该缓存起来,不要每次循环都调用
GetMethodID(影响性能)
二、完整可运行实现(两种交互模式)
第一步:Java侧代码(MainActivity.java)
package org.foo.bar; import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { // 加载JNI库 static { System.loadLibrary("native-lib"); } // 模式1:Java调用C函数,传入int参数(摇杆输入) public native void sendJoystickInput(int inputValue); // 模式2:C侧调用Java函数获取摇杆状态 public int getJoystickState() { // 这里模拟摇杆输入,实际替换成你的摇杆逻辑 Log.e("JniHandler", "getJoystickState WAS CALLED!!!"); return 333; // 返回你的摇杆状态值 } // 启动C侧的独立循环线程 public native void startNativeLoop(); // C侧回调Java的示例方法(比如更新UI) public void updateTimer() { Log.e("JniHandler", "updateTimer WAS CALLED from C!!!"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 测试模式1:Java调用C传int sendJoystickInput(100); // 启动模式2:C侧独立循环 startNativeLoop(); } }
第二步:JNI侧代码(native-lib.cpp)
#include <string.h> #include <inttypes.h> #include <pthread.h> #include <jni.h> #include <android/log.h> #include <assert.h> #include <sys/time.h> // 日志宏,替换SDL_Log #define LOG_TAG "JNI_DEBUG" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) typedef struct tick_context { JavaVM *javaVM; jclass mainActivityClz; // 缓存MainActivity的类引用 jobject mainActivityObj; // 缓存MainActivity的对象全局引用 pthread_mutex_t lock; int done; // 缓存方法ID,避免每次循环重复获取 jmethodID getJoystickStateId; jmethodID updateTimerId; } TickContext; TickContext g_ctx; // JNI_OnLoad:JVM加载时初始化 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; memset(&g_ctx, 0, sizeof(g_ctx)); LOGD("DEBUG -- JNI INIT!!!"); g_ctx.javaVM = vm; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { LOGE("JNI版本不支持"); return JNI_ERR; } // 查找MainActivity类(注意类名是完整的,用/分隔) jclass clz = env->FindClass("org/foo/bar/MainActivity"); if (!clz) { LOGE("找不到MainActivity类"); return JNI_ERR; } g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz); env->DeleteLocalRef(clz); // 初始化互斥锁 pthread_mutex_init(&g_ctx.lock, NULL); g_ctx.done = 0; return JNI_VERSION_1_6; } // 模式1:Java调用C函数,传入int参数 extern "C" JNIEXPORT void JNICALL Java_org_foo_bar_MainActivity_sendJoystickInput(JNIEnv *env, jobject instance, jint inputValue) { LOGD("Java调用C,传入的摇杆值:%d", inputValue); // 这里执行你的NDK逻辑,比如处理摇杆输入 // ... } // C侧独立循环线程函数 void* NativeLoop(void* context) { LOGD("DEBUG -- NativeLoop IS CALLED"); TickContext *pctx = (TickContext*) context; JavaVM *javaVM = pctx->javaVM; JNIEnv *env; jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6); // 线程Attach到JVM if (res != JNI_OK) { res = javaVM->AttachCurrentThread(&env, NULL); if (JNI_OK != res) { LOGE("FAILED TO ATTACH THREAD"); return NULL; } } // 缓存方法ID(只需要获取一次) if (!pctx->getJoystickStateId) { pctx->getJoystickStateId = env->GetMethodID(pctx->mainActivityClz, "getJoystickState", "()I"); if (!pctx->getJoystickStateId) { LOGE("获取getJoystickState方法ID失败"); goto exit; } } if (!pctx->updateTimerId) { pctx->updateTimerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V"); if (!pctx->updateTimerId) { LOGE("获取updateTimer方法ID失败"); goto exit; } } // 独立循环逻辑 struct timeval beginTime, curTime, usedTime, leftTime; const struct timeval kOneSecond = {1, 0}; while(1) { gettimeofday(&beginTime, NULL); pthread_mutex_lock(&pctx->lock); int done = pctx->done; pthread_mutex_unlock(&pctx->lock); if (done) { break; } // 模式2:C调用Java函数获取摇杆状态 jint joystickState = env->CallIntMethod(pctx->mainActivityObj, pctx->getJoystickStateId); LOGD("C调用Java获取摇杆状态:%d", joystickState); // 执行你的NDK逻辑,比如根据摇杆状态处理操作 // ... // 回调Java的updateTimer方法 env->CallVoidMethod(pctx->mainActivityObj, pctx->updateTimerId); // 控制循环频率(每秒一次) gettimeofday(&curTime, NULL); timersub(&curTime, &beginTime, &usedTime); timersub(&kOneSecond, &usedTime, &leftTime); struct timespec sleepTime; sleepTime.tv_sec = leftTime.tv_sec; sleepTime.tv_nsec = leftTime.tv_usec * 1000; if (sleepTime.tv_sec <= 1) { nanosleep(&sleepTime, NULL); } else { LOGE("NativeLoop processing too long!"); } } exit: // 线程Detach JVM javaVM->DetachCurrentThread(); LOGD("NativeLoop线程退出"); return context; } // 启动C侧独立循环的JNI方法 extern "C" JNIEXPORT void JNICALL Java_org_foo_bar_MainActivity_startNativeLoop(JNIEnv *env, jobject instance) { LOGD("JNI CALL START NativeLoop"); pthread_t threadInfo_; pthread_attr_t threadAttr_; pthread_attr_init(&threadAttr_); pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED); // 创建MainActivity对象的全局引用(因为线程中要使用) if (!g_ctx.mainActivityObj) { g_ctx.mainActivityObj = env->NewGlobalRef(instance); if (!g_ctx.mainActivityObj) { LOGE("创建MainActivity全局引用失败"); return; } } // 创建线程 int result = pthread_create(&threadInfo_, &threadAttr_, NativeLoop, &g_ctx); assert(result == 0); pthread_attr_destroy(&threadAttr_); } // JNI_OnUnload:释放资源 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return; } // 释放全局引用 if (g_ctx.mainActivityClz) { env->DeleteGlobalRef(g_ctx.mainActivityClz); g_ctx.mainActivityClz = NULL; } if (g_ctx.mainActivityObj) { env->DeleteGlobalRef(g_ctx.mainActivityObj); g_ctx.mainActivityObj = NULL; } // 销毁互斥锁 pthread_mutex_destroy(&g_ctx.lock); LOGD("JNI资源释放完成"); }
三、关键注意事项
方法签名准确性:
- 用
javap -s -p org.foo.bar.MainActivity生成准确的方法签名,避免手动写错 - 比如
getJoystickState()的签名是()I,updateTimer()是()V,sendJoystickInput(int)是(I)V
- 用
全局引用管理:
- 线程中要使用的Java对象/类必须创建全局引用,不能用局部引用(局部引用在JNI函数返回后会被回收)
- 在
JNI_OnUnload中必须释放全局引用,避免内存泄漏
线程Attach/Detach:
- 非JNI创建的线程(比如你用pthread创建的)必须先
AttachCurrentThread才能调用JNI函数 - 线程退出前必须
DetachCurrentThread,避免资源泄漏
- 非JNI创建的线程(比如你用pthread创建的)必须先
方法ID缓存:
GetMethodID/GetStaticMethodID是耗时操作,建议在初始化或第一次使用时缓存,不要每次循环都调用
四、验证运行
- 确保你的Android项目正确配置NDK(在
build.gradle中指定NDK版本) - 将JNI代码放在
app/src/main/cpp/目录下 - 运行App,查看Logcat的
JNI_DEBUG标签,应该能看到:- Java调用C的日志:
Java调用C,传入的摇杆值:100 - C调用Java的日志:
getJoystickState WAS CALLED!!!、C调用Java获取摇杆状态:333 - C回调Java的日志:
updateTimer WAS CALLED from C!!!
- Java调用C的日志:
内容的提问来源于stack exchange,提问作者LUser
相关产品推荐
相关产品推荐

