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

    message(WARNING  " add definition ")
    
    • 1

    参考:cmake_message

    cmake增加宏

    add_definitions(-DDEBUG) 是定义宏-D后面是宏的名称,在c++代码中我们可以使用ifdef DEBUG 来使用我们的编译参数

    字符串比较
    if ("${variable}" STREQUAL "true"){
    }else{
    }
    
    • 1
    • 2
    • 3

    获取编译参数重传递到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"
    
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    参考:
    https://blog.csdn.net/ljx1400052550/article/details/117280541

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

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

    导入头文件
    set(headerDirPath "${CMAKE_CURRENT_LIST_DIR}/xxx/xx/xx/include/")
    include_directories(${headerDirPath})
    
    • 1
    • 2

    在 cpp 文件中引入的时候我们总 include 的下一级目录开始导入头文件

    #include "dir1/xxx.h"
    
    • 1

    通过javah生成native对应的c++头文件

    在方法参数前添加native关键字
    例如:

        public native String get();
    
    • 1

    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);
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

        const char *str = "hello, world";
        return env->NewStringUTF(str);
    
    • 1
    • 2

    在这里插入图片描述
    方法签名生成:
    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();
    
    • 1
    静态注册

    生成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__)
    
    • 1
    • 2
    • 3
    动态注册

    动态注册我们在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();
        }
    
    • 1
    • 2
    • 3
    • 4

    链接:

    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 .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的-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)
    -s --syms Display the symbol table(查看符号表)

    objdump 反汇编so文件

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

    c++filt

    c++filt 可用于解析 C++ 和 Java 中被修饰的符号,比如变量与函数名称。

    -n, --no-strip-underscore
    不删除初始下划线

    nm 命令查看so中的符号

    nm --help

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

  • 相关阅读:
    Leetcode刷题1373. 二叉搜索子树的最大键值和
    GitHub上8.5K 收藏! Python 代码内存分析的利器
    FFmpeg编译支持x264/openH264/dash
    Android Qcom Sensor架构学习
    阿里笔试——总结2
    【版本控制工具二】Git 和 Gitee 建立联系
    (学习日报)2022.7.19
    Redis 6学习笔记(上)
    lv5 嵌入式开发-2 exec函数族
    2022款华硕灵耀pro16和华硕proart创16区别哪个好哪个更值得入手
  • 原文地址:https://blog.csdn.net/liu_12345_liu/article/details/126794797