Java中native方法声明必须用native关键字修饰、无方法体、以分号结尾;所在类需在静态块中调用System.loadLibrary("xxx")加载库,且C++函数名须严格按Java_包名_类名_方法名规则拼写,否则抛UnsatisfiedLinkError。

必须先声明 native 方法,再实现 C++ 函数,且函数签名要严格匹配,否则 UnsatisfiedLinkError 会直接崩溃。
Java 层怎么写 native 方法声明
native 方法必须是 public、无方法体、带 native 修饰符;所在类需通过 System.loadLibrary("xxx") 加载对应库名(不含 lib 前缀和 .so 后缀)。
常见错误现象:
- 忘记
static { System.loadLibrary("native-lib"); }→ 运行时报java.lang.UnsatisfiedLinkError: No implementation found - 库名写成
"libnative-lib.so"或"native-lib.so"→ 加载失败,因为 Android 只认纯模块名 - 方法名/参数类型/返回值与 C++ 不一致 → 找不到对应函数,同样触发
UnsatisfiedLinkError
示例(MainActivity.java):
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib"); // ← 注意:这里填 "native-lib",不是 "libnative-lib.so"
}
<pre class="brush:php;toolbar:false;">// ← 签名决定 C++ 函数名,不能加 default 或其他修饰
public native String stringFromJNI();
public native int addNumbers(int a, int b);}
Android 开发调试技能,通过系统 ADB 工具操作 Android 设备。以下场景必须触发此技能:(1) 直接 ADB 操作——安装 APK、查看设备列表、抓取 logcat 日志、查看已安装应用、清除应用数据、截图、重启设备、拉取/推送文件、查看 CPU/内存/电池信息、adb shell 操作;(2)...
C++ 层函数名怎么生成才不被找不到
JNI 要求 C++ 函数名按固定格式拼接:Java_包名_类名_方法名,其中包名里的 . 替换为 _,方法名中的下划线 _ 要转义为 _1。这是硬编码规则,CMake 不会帮你校验。
容易踩的坑:
- 手写函数名时漏掉包路径或写错大小写(如
com_example_app写成com.example.app)→ 符号未定义 - Java 方法名含下划线(如
get_user_id),没改成get_1user_1id→ 找不到符号 - 用
extern "C"包裹但忘了加JNIEXPORT和JNICALL宏 → 编译能过,但符号不可见
示例(native-lib.cpp):
#include <jni.h><p>extern "C" {</p>
<p>// Java_com_example_myapp_MainActivity_stringFromJNI
JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv <em>env, jobject /</em> this */) {
return env->NewStringUTF("Hello from C++");
}</p>
<p>// Java_com_example_myapp_MainActivity_addNumbers
JNIEXPORT jint JNICALL
Java_com_example_myapp_MainActivity_addNumbers(JNIEnv <em>env, jobject /</em> this */, jint a, jint b) {
return a + b;
}</p>
<p>}</p></jni.h>
如何避免 JNIEnv 和线程安全问题
JNIEnv* 是线程局部变量,只在当前调用线程有效。跨线程传 JNIEnv* 指针或缓存它,运行时大概率 crash。
使用场景与应对方式:
- 回调 Java 方法(如从子线程通知 UI)→ 必须用
JavaVM*保存(全局唯一),在子线程中调用GetEnv或AttachCurrentThread - 频繁调用 JNI 函数 → 避免反复 Attach/Detach,可复用已 Attach 的线程,或改用主线程同步调用
- 传递对象引用(
jobject)→ 默认是 local ref,出函数自动释放;跨函数或跨线程需转为GlobalRef并手动DeleteGlobalRef
关键点:
-
JavaVM*可通过env->GetJavaVM(&jvm)获取,应作为全局变量或单例持有 - 不要在 C++ 构造函数/析构函数里调用 JNI 函数,除非你 100% 确保
JNIEnv*有效
CMakeLists.txt 怎么关联头文件和链接参数
如果 C++ 代码用了 STL(如 std::string、std::vector)或 Android 系统库(如 log.h),CMakeLists.txt 必须显式声明,否则编译报错或链接失败。
参数差异直接影响能否编译通过:
-
APP_STL在Application.mk中控制,但用 CMake 时得在CMakeLists.txt里用add_library的SHARED后加-DANDROID_STL=c++_shared,或在build.gradle的cppFlags中指定 -
target_link_libraries必须包含log才能用__android_log_print;缺了就报undefined reference to '__android_log_print' - 头文件路径没用
include_directories或target_include_directories暴露 →fatal error: 'xxx.h' file not found
最小可用片段(CMakeLists.txt):
cmake_minimum_required(VERSION 3.10.2)
<h1>声明库</h1><p>add_library(native-lib SHARED native-lib.cpp)</p><h1>链接系统库</h1><p>find_library(log-lib log)
target_link_libraries(native-lib ${log-lib})</p><h1>若用了 STL,确保 build.gradle 中 cppFlags 包含:</h1><h1>"-DANDROID_STL=c++_shared"</h1><h1>否则 std::string 构造可能 crash</h1>
最常被忽略的是:System.loadLibrary() 的时机和位置——它必须在任何 native 方法被调用前完成,且最好放在类静态块里;而 C++ 函数名拼写错误不会在编译时报错,只会在运行时炸开,调试成本极高。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










