文章目录
- cmake语法基础
- cmake添加日志:
- cmake增加宏
- 字符串比较
- cmake在build.gradle中传递编译参数到cmake
- 通过javah生成native对应的c++头文件
- jni和java之间字符串的相互操作
- JavaVM和JNIEnv
- 字符串的编码
- native方法静态注册和动态注册
- 静态注册
- 动态注册
- extern c
- C++中STATIC和SHARE库类型的区别
- c++控制so导出的函数符号的可见性
- jni处理异常
- NDK工具使用:
根据日常学习持续更新中
cmake语法基础
cmake添加日志:
message([] “message text” …)
Record the specified message text in the log. If more than one message string is given, they are concatenated into a single message with no separator between the strings.
mode参数可以有不同的选项,一般不会选择ERROR级别,ERROR会停止cmake运行
- WARNING: CMake Warning, continue processing.
- 还有其他很多mode可以参考下面的
cmake_message
如果要在日志中打印变量的值的话可以使用${}在引号中包裹变量
message(CHECK_FAIL “missing components: ${variable}”)
参考:cmake_message
cmake增加宏
add_definitions(-DDEBUG) 是定义宏-D后面是宏的名称,在c++代码中我们可以使用ifdef DEBUG 来使用我们的编译参数
字符串比较
if ("${variable}" STREQUAL "true"){
}else{
}
获取编译参数重传递到cmake的值,然后比较字符串然后进行判断
cmake在build.gradle中传递编译参数到cmake
//cmake 的参数配置入口
externalNativeBuild {
cmake {
// 指定一些编译选项
cppFlags "-std=c++11 -frtti -fexceptions"
//如何向变量传递参数,对应的格式如下(arguments "-D变量名=参数")
arguments '-DANDROID_PLATFORM=android-24', '-DANDROID_STL=c++_static', '-DANDROID_STL=c++_shared'
// 也可以使用下面这种语法向每个变量传递多个参数(参数之间使用空格隔开),格式如下
// arguments "-D变量名=参数1 参数2"
arguments "-DANDROID_CPP_FEATURES=rtti exceptions"
}
}
参考:
https://blog.csdn.net/ljx1400052550/article/details/117280541
通过javah生成native对应的c++头文件
在方法参数前添加native关键字
例如:
public native String get();
javah 输入命令的目录需要是包名的根目录,也就是需要包含包名
终端路径:/Users/lxd/code/Android/lxdAndroidStart/app/src/main/java
命令:javah com.example.androidstart.JniTest
jni和java之间字符串的相互操作
const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
int len = env->GetStringUTFLength(jstr);
printf("from java str=%s, len=%d", str, len);
env->ReleaseStringUTFChars(jstr, str);
const char *str = "hello, world";
return env->NewStringUTF(str);
方法签名生成:
javap
-s 输出内部类型签名
传入-s后面的参数需要是classes,可以通过javac获取
javac 编译java文件生成class文件/或者可以去项目编译中的中间产物中去寻找class文件
c++ lambda
Lambda表达式完整的声明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
- capture list:捕获外部变量列表
- params list:形参列表
- mutable指示符:用来说用是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
链接:
https://www.cnblogs.com/DswCnblog/p/5629165.html
JavaVM和JNIEnv
JavaVM
JavaVM再Android中只有一个,JavaVM带有函数表,允许你创建和销毁JavaVM。
JNIEnv
JNIEnv提供了大多数的JNI函数,对于C语言的代码,本地函数都需要接收JNIEnv为第一个参数,而对于C++,JNIEnv不需要作为参数传入
JNIEnv用做线程私有存储,因此,不能在线程间共享JNIEnv变量,如果一个代码块没有JNIEnv,可以通过JavaVM去获取
在jni.h的定义中,针对c++和c的不同,有着不同的定义,因此两种语言混用的时候需要注意。
字符串的编码
java中字符串使用的是UTF-16编码,
JNI中使用 utf-8 表示字符串,UTF-8是变长编码的unicode,一般ascii字符是1字节,中文是3字节;
c/c++使用的是原始数据,ascii就是一个字节了,中文一般是GB2312编码,用两个字节来表示一个汉字。
所以三种类型的字符串如果含有中文的时候需要特殊转换下
native方法静态注册和动态注册
首先我们在java中使用native关键字声明这个方法是native方法,然后使用静态注册或者动态注册,将native方法和c++实现绑定
public native void nativeStaticRegister();
静态注册
生成native方法对应的c++头文件
使用javah生成class文件对应的头文件,-d 第一个参数是输出路径,第二个参数是src目录下的类的全名
在对应的Terminal路径输入命令,我的路径是这个/Users/XXX/code/Android/NativeJni/app/src/main/java
javah -d …/cpp/ com.example.nativejni.CallBackClass
输入了上面的命令后就会在 cpp 目录下生成对应的cpp头文件
直接cpp文件中输入native方法名,as会提示回车后自动补全
或者我们将公共部分提出来,写成一个宏,然后使用宏
#define FFMPEG_FUNC(RETURN_TYPE, FUNC_NAME, ...) \
JNIEXPORT RETURN_TYPE JNICALL Java_com_example_nativejni_MainActivity_##FUNC_NAME \
(JNIEnv *env, jclass thiz, ##__VA_ARGS__)
动态注册
动态注册我们在JNI_OnLoad方法中使用RegisterNatives进行注册,将java的native方法和c++进行绑定。
因为绑定的时候需要字节码的方法签名:
获取方法签名的方式
extern c
c++中jni的方法前都有个这个关键字,
不带extern c编译出来的so中的符号
_Z46Java_com_example_nativejni_MainActivity_getvalP7_JNIEnvP8_jobject
带extern c
Java_com_example_nativejni_MainActivity_getval
带extern c编译出来的符号才符合jni命名,extern c让编译器使用c的编译规则编译指定代码
查看so中符号的方法:
在我们的ndk目录下,比如我的路径是
ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/
aarch64-linux-android-nm
在这个terminal执行 ./aarch64-linux-android-nm so路径
就会展示出so的符号列表(对于debug包apk中解压出来的so自己试了下需要加上-D参数才能显示动态链接符号)
./aarch64-linux-android-nm --help 查看-D参数的含义
-D, --dynamic Display dynamic symbols instead of normal symbols
参考:https://blog.csdn.net/sinat_36817189/article/details/110423243
C++中STATIC和SHARE库类型的区别
STATIC静态库:变异的时候会将程序和静态库进行链接,可执行程序中会包含当前的静态库,多个可执行程序会有多份静态库。
SHARED动态库:动态库的调用和链接是在运行时,可执行程序中并不包含动态库,多个可执行程序共享一份动态库
c++控制so导出的函数符号的可见性
-
当-fvisibility=hidden时动态库中的函数默认是被隐藏的即 hidden. 除非显示声明为
__attribute__((visibility("default")))
. -
当-fvisibility=default时动态库中的函数默认是可见的.除非显示声明为
__attribute__((visibility("hidden")))
jni处理异常
if (env->ExceptionOccurred()) {
LOGI("occurred lxd exception");
env->ExceptionClear();
}
可以使用
DirectBuffer实现native和c++层的更高效的数据传递,但是堆外内存的创建和销毁比较耗时。
链接:
java JNI官方教程
NDK工具使用:
ndk-stask查看崩溃堆栈
$NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi-v7a -dump foo.txt
上面的foo.txt指的是崩溃的堆栈,可以从崩溃的日志中拷贝出来,要从*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***开始拷贝,要包含这个
./ndk-stack --help
usage: ndk-stack.py [-h] -sym SYMBOL_DIR [-i INPUT]
Symbolizes Android crashes.
optional arguments:
-h, --help show this help message and exit
-sym SYMBOL_DIR, --sym SYMBOL_DIR
directory containing unstripped .so files
-i INPUT, -dump INPUT, --dump INPUT
input filename
See <https://developer.android.com/ndk/guides/ndk-stack>.
上面的-sym传入的SYMBOL_DIR要求是unstripped,unstripped是啥意思呢
在我们编译so生成的产物下面,cmake的产物没有strip,so会大很多
striped目录下面会有去处符号的so,体积会小很多
/Users/lxd/Library/Android/sdk/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin
strip工具目录
链接:
官方ndk-stack使用教程
https://blog.csdn.net/yangzex/article/details/126581161
addr2line查看代码位置
// 0x12345678为堆栈地址,替换为实际崩溃地址我们可以查看到我们的代码崩溃的位置
aarch64-linux-android-addr2line -e libxxx.so 0x12345678
readelf -d libxxx.so查看其依赖库:
./aarch64-linux-android-readelf --help
-d --dynamic Display the dynamic section (if present)
objdump 反汇编so文件
./arm-linux-androideabi-objdump –S libxx.so