Android NDK开发基础

文章目录

      • cmake语法基础
        • cmake添加日志:
        • cmake增加宏
        • 字符串比较
        • cmake在build.gradle中传递编译参数到cmake
        • 指定单个文件的编译配置
      • 通过javah生成native对应的c++头文件
      • jni和java之间字符串的相互操作
      • JavaVM和JNIEnv
      • 字符串的编码
      • native方法静态注册和动态注册
        • 静态注册
        • 动态注册
      • extern c
      • C++中STATIC和SHARE库类型的区别
      • c++控制so导出的函数符号的可见性
      • jni处理异常
      • NDK工具使用:
        • JNI heap

根据日常学习持续更新中

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}”)

message(WARNING  " add definition ")

参考: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

指定单个文件的编译配置
set_source_files_properties(file1.cpp PROPERTIES COMPILE_FLAGS "-std=c++14")

例如我们可以使用上面的配置让单个文件使用c14去编译。
set_source_files_properties

通过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的不同,有着不同的定义,因此两种语言混用的时候需要注意。
Android NDK开发基础_第1张图片

字符串的编码

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会提示回车后自动补全
Android NDK开发基础_第2张图片
或者我们将公共部分提出来,写成一个宏,然后使用宏

#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导出的函数符号的可见性

  1. 当-fvisibility=hidden时动态库中的函数默认是被隐藏的即 hidden. 除非显示声明为__attribute__((visibility("default"))).

  2. 当-fvisibility=default时动态库中的函数默认是可见的.除非显示声明为__attribute__((visibility("hidden")))

jni处理异常

    if (env->ExceptionOccurred()) {
        LOGI("occurred lxd exception");
        env->ExceptionClear();
    }

链接:

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 .

上面的-sym传入的SYMBOL_DIR要求是unstripped,unstripped是啥意思呢

在我们编译so生成的产物下面,cmake的产物没有strip,so会大很多
Android NDK开发基础_第3张图片
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)
-s --syms Display the symbol table(查看符号表)

objdump 反汇编so文件

./arm-linux-androideabi-objdump –S libxx.so

JNI heap

参考:
https://blog.csdn.net/u011686167/article/details/124132719

你可能感兴趣的:(android,android)