Android-NDK-002-编译so方式

256 阅读6分钟

使用android-ndk-r22b进行交叉编译

1 指定格式说明

--sysroot=XX 使用xx作为这一次编译的头文件与库文件的查找目录,查找下面的 usr/include usr/lib目录,--sysroot即可指定头文件又可指定库文件 -isysroot XX 指定头文件查找目录,覆盖--sysroot ,查找 XX/usr/include目录下头文件 -isystem XX 指定头文件查找路径(直接查找根目录) -IXX 头文件查找目录,I是大写的

2 编译流程

2.1 window10 x86_64 -> android linux arm 流程示例

  • 1 在NDK文件路径下执行编译命令: D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin

  • 2 指定输出文件-o D:\Project\C\Practice_C\libmain

.\arm-linux-androideabi-gcc.exe -o D:\Project\C\Practice_C\libmain D:\Project\C\Practice_C\main.c

执行后: image.png

  • 3 指定头文件查找路径-isystem .\sysroot\usr\include
.\arm-linux-androideabi-gcc.exe -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include -o D:\Project\C\Practice_C\libmain D:\Project\C\Practice_C\main.c

执行后: image.png

  • 4 指定.h文件查找路径-isystem .\sysroot\usr\include\arm-linux-androideabi
.\arm-linux-androideabi-gcc.exe -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include\arm-linux-androideabi -isystem D:\Android\android-ndk-r17c\sysroot\usr\include -o D:\Project\C\Practice_C\libmain D:\Project\C\Practice_C\main.c
  • 5 指定头文件的so库查找目录--sysroot=.\platforms\android-22\arch-arm
.\arm-linux-androideabi-gcc.exe --sysroot=D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\platforms\android-22\arch-arm -lc -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include\arm-linux-androideabi -o D:\Project\C\Practice_C\libmain D:\Project\C\Practice_C\main.c

执行后:生成交叉编译库libmain image.png

  • 6 生成可在Android上执行的库
.\arm-linux-androideabi-gcc.exe --sysroot=D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\platforms\android-22\arch-arm -lc -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include\arm-linux-androideabi -pie -o D:\Project\C\Practice_C\libmain D:\Project\C\Practice_C\main.c
  • 7 生成so库
.\arm-linux-androideabi-gcc.exe --sysroot=D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\platforms\android-22\arch-arm -lc -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include -isystem D:\Develop\Android\adt-bundle-windows-x86_64\sdk\ndk\android-ndk-r17c\sysroot\usr\include\arm-linux-androideabi -fPIC -shared -o D:\Project\C\Practice_C\libmain.so D:\Project\C\Practice_C\main.c

3 使用CMAKE简化编译so流程

3.1 安装必要的工具:

CMake : NDK构建工具 LLDB : NDK调试工具 NDK : NDK开发工具

image

3.2.创建NDK项目

image

勾选下【c++ support】 然后一路next 到【Customize C++ Support】设置:

image

可以看到三个选项:

  • C++ Standard:C++标准,选择【Toolchain Default】会使用默认的CMake配置。
  • Exceptions Support:支持C++异常处理,标志为 -fexceptions。
  • Runtime Type Information Support:支持运行时类型识别,标志为-frtti,程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。

在这里我们使用默认C++标准,不勾选下面的两个选项,点击【Finish】按钮进入下一个环节。

3.3.NDK项目

项目目录:

image.png

gradle 配置 : image.png

对CMakeLists.txt不太了解的稍后可以移步:CMakeLists.text详解

这里项目创建完成后,Android studio会自动给我们生成了一份配置好了的CMakeLists.text文件


cmake_minimum_required(VERSION 3.10.2)  # CMake 版本要求

project(MyNativeProject)  # 项目名称

# 设置 Android NDK 的最低版本
set(CMAKE_ANDROID_NDK ${ANDROID_NDK})

# 指定源文件
set(SOURCE_FILES
    src/main/cpp/native-lib.cpp  # 添加 C++ 源文件
)

# 设置编译选项(例如,启用 C++11)
set(CMAKE_CXX_STANDARD 11)

# 指定要构建的共享库
add_library(
    native-lib  # 生成的库名
    SHARED  # 构建共享库
    ${SOURCE_FILES}  # 源文件列表
)

# 设置 Android NDK 中的依赖库
find_library(
    log-lib  # 库的名字
    log  # 查找 log 库
)

# 链接库
target_link_libraries(
    native-lib  # 要链接的目标库
    ${log-lib}  # 依赖库
)


CMakeLists.txt关键命令详解:

  1. cmake_minimum_required(VERSION 3.10.2) :

    • 设置所需的最低 CMake 版本。这个版本要求确保支持构建 Android 原生代码的功能。
  2. project(MyNativeProject) :

    • 定义项目的名称,通常会显示在构建过程的日志和输出中。
  3. set(CMAKE_ANDROID_NDK ${ANDROID_NDK}) :

    • 设置 Android NDK 的路径,通常你不需要手动设置这个,因为在 build.gradle 中已配置 ndkVersion,CMake 会自动找到 NDK 的路径。
  4. set(SOURCE_FILES ...) :

    • 定义源代码文件列表,这里列出了 C++ 源文件(例如 native-lib.cpp),这些文件将在构建时被编译。
  5. add_library() :

    • 用于定义要生成的库文件。

      • 第一个参数是库的名称(native-lib)。
      • 第二个参数 SHARED 表示这是一个共享库(.so 文件)。如果是静态库则使用 STATIC
      • 第三个参数是源文件列表,可以是一个或多个源文件。
  6. find_library() :

    • 用来查找系统中已经存在的库文件。这里使用 log 库,log-lib 将被设置为 Android 中的日志库(liblog.so),它可以提供 __android_log_print() 函数等日志功能。
  7. target_link_libraries() :

    • 将需要链接的库文件指定给目标库(如 native-lib)。这里将 Android 日志库 log-lib 链接到 native-lib

同时比普通的android项目多出了一个cpp文件夹和其路径下的native-lib.cpp源码文件。 源码定义了一个方法,方法名是通过Java_包名类名方法名 的方式命名的 native-lib.cpp,代码如下:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_ing_ndklearn_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

其作用是返回一个字符串给Java层调用。

现在CmakeLists.txt 和 源码文件 AndroidStudio已经都帮我们准备好了,万事俱备,只欠东风,此刻你就是诸葛在世,羽扇纶巾,设坛作法,轻点一下🔨进行编译 -- 【Make project】稍等片刻就看到东西南北so文件了...

image.png

so库文件生成了,如何调用,方法如下:

package com.ing.ndklearn;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

然后运行后看效果,这里就略了,效果就是页面显示“Hello from c++ ”。

3.4 题外话

这里的MainActivity 是默认生成的,即入口Activity,也是java层的调用类,如果你觉得怪,可以将其中jni的代码抽离出来,单独创建一个调用类,比如命名为NDKLibrary :

package com.ing.ndklearn;

/**
 * Created by ing on 2019/7/9
 */
public class NDKLibrary {
    static {
        System.loadLibrary("native-lib");
    }

    public static native String stringFromJNI();
}

那么同时要修改下native-lib.cpp 将

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_ing_ndklearn_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

替换成

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_ing_ndklearn_NDKLibrary_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

重新Make Project下,生成新的so库。 此时,MainActivity作为调用方修改为如下

package com.ing.ndklearn;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
//
//    // Used to load the 'native-lib' library on application startup.
//    static {
//        System.loadLibrary("native-lib");
//    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(NDKLibrary.stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
//    public native String stringFromJNI();
}

运行后与之前的效果一致,页面显示“Hello from c++ ”。

3.5 总结一下整个过程

  1. Gradle 调用您的外部构建脚本 CMakeLists.txt
  2. CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其打包到 APK 中。
  3. 运行时,应用的 MainActivity 会使用 System.loadLibrary()加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()
  4. MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新 TextView

以上,就是利用AndroidStudio 默认的CMake方式构建NDK项目,虽然简单,但对于我们去认识理解NDK开发是很有用的。