Android Native 开发

2 minute read

Android Native 开发

Android ndk guides

1. Create new native project / add native code

add native code

2. Build tools

2.1 Build using ndk-build

ndk-build ndk-build 脚本使用 NDK 的基于 Make 的构建系统构建项目。ndk-build 使用 Android.mkApplication.mk 配置.

Android.mk

documentation

Application.mk

documentation

Final build script

ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=./Application.mk

或者将代码和*.mk文件放到jni目录下,直接 ndk-build.

2.2 (New Method) Build using CMake

documentaion
Create a file named “CMakeLists.txt”

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

#  向 CMake 构建脚本添加 find_library() 命令以找到 NDK 库并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。以下示例会找到 Android 专有的日志支持库,并将其路径存储在 log-lib 中:

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

# 为了让您的原生库能够调用 log 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联这些库: 
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

# 添加预构建库的步骤与为 CMake 指定其他要构建的原生库的步骤相似。不过,由于库已构建,因此您需要使用 IMPORTED 标志指示 CMake 您只想要将此库导入到您的项目中:

add_library( imported-lib
             SHARED
             IMPORTED )

# 然后,您需要使用 set_target_properties() 命令指定库的路径,具体命令如下所示。

# 某些库会针对特定的 CPU 架构或应用二进制接口 (ABI) 提供单独的软件包,并将其整理到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让您只使用所需的库版本。如需向 CMake 构建脚本添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,您可以使用 ANDROID_ABI 路径变量。此变量使用的是 NDK 支持的一组默认 ABI,或者您手动配置 Gradle 而让其使用的一组经过过滤的 ABI。例如:

set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

# 为了让 CMake 能够在编译时找到头文件,您需要使用 include_directories() 命令并包含相应头文件的路径:

include_directories( imported-lib/include/ )
# 如需将预构建库关联到您自己的原生库,请将其添加到 CMake 构建脚本的 target_link_libraries() 命令中.

从命令行调用 CMake
工具链参数

cmake
-Hpath/to/cmakelists/folder
-Bpath/to/generated/ninja/project/debug/ABI
-DANDROID_ABI=ABI                               // For example, arm64-v8a
-DANDROID_PLATFORM=platform-version-string      // For example, android-16,and this is the default value
-DANDROID_NDK=android-sdk/ndk/ndk-version
-DCMAKE_TOOLCHAIN_FILE=android-sdk/ndk/ndk-version/build/cmake/android.toolchain.cmake
-G Ninja

注意:如果 CMake 无法找到 Ninja 可执行文件,请使用以下可选参数:

-DCMAKE_MAKE_PROGRAM=path/to/ninja/ninja.exe

然后执行 ninja

3. Register native functions

documentation

3.1 JNI_OnLoad

运行时可以通过两种方式找到您的原生方法。您可以使用 RegisterNatives 显示注册原生方法,也可以让运行时使用 dlsym 进行动态查找。RegisterNatives 的优势在于,您可以预先检查符号是否存在,而且还可以通过只导出 JNI_OnLoad 来获得规模更小、速度更快的共享库。让运行时发现函数的优势在于,要编写的代码稍微少一些。

如需使用 RegisterNatives,请执行以下操作:

  • 提供 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) 函数。
  • JNI_OnLoad 中,使用 RegisterNatives 注册所有原生方法。
  • 使用 -fvisibility=hidden 进行构建,以便只从您的库中导出您的 JNI_OnLoad。这将生成速度更快且规模更小的代码,并避免与加载到应用中的其他库发生潜在冲突(但如果应用在原生代码中崩溃,则创建的堆栈轨迹用处不大)。 静态初始化程序应如下所示:
      static {
          System.loadLibrary("fubar");
      }
    

    如果使用 C++ 编写,JNI_OnLoad 函数应如下所示:

    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
          JNIEnv* env;
          if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
              return JNI_ERR;
          }
    
          // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
          jclass c = env->FindClass("com/example/app/package/MyClass");
          if (c == nullptr) return JNI_ERR;
    
          // Register your class' native methods.
          static const JNINativeMethod methods[] = {
              {"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)},
              {"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)},
          };
          int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
          if (rc != JNI_OK) return rc;
    
          return JNI_VERSION_1_6;
      }
    

    如需改为使用“发现”原生方法,您需要以特定方式为其命名(详情请参阅 JNI 规范)。这意味着,如果方法签名是错误的,您要等到第一次实际调用该方法时才会知道。
    JNI_OnLoad 进行的任何 FindClass 调用都会在用于加载共享库的类加载器的上下文中解析类。从其他上下文调用时,FindClass 会使用与 Java 堆栈顶部的方法相关联的类加载器,如果没有(因为调用来自刚刚附加的原生线程),则会使用“系统”类加载器。由于系统类加载器不知道应用的类,因此您将无法在该上下文中使用 FindClass 查找您自己的类。这使得 JNI_OnLoad 成为查找和缓存类的便捷位置:一旦有了有效的 jclass,您就可以从任何附加的线程使用它。

    3.2 Java_com_example_myapplication_MethodName

4. JNI

jni-intro
jni documentation

Comments