望远山,知近路,而后自得其乐!

Android JNI开发工具篇(1)-开发环境搭建

工欲善其事,必先利其器

1. 开发环境准备

搭建开发环境是我们进行开发前首先要完成的任务,进行Android jni开发,依赖的基本开发环境包括:

  1. Android sdk
  2. android ndk
  3. cmake
  4. android studio

Android studio的sdk manager已经包括了上面所说的sdk,ndk,cmake等工具的安装,所以一般只用下载android studio,然后再使用sdk manager工具下载这些工具就可以了。

默认情况下,Android studio使用的编译工具是cmake,但很多沿用的项目都是使用NDK的ndk-build工具来编译的,所以android studio也支持ndk-build。

2. 使用android studio创建本地C++工程

1.新建工程, 在向导的 Choose your project 部分中,选择Natvie C++ 项目类型 。

2.在设置工程名,包名,保存路径和语言,此处我们选择Java语言。


3.在向导的 Customize C++ Support 部分中,可以选则C++ Toolchain,一般情况下,选择默认就可以,如果开发中需要用到C++11,或者c++14等一些较高级的C++标准的特性时,可以选择对应的Toolchain

4.点击finish,开始构建工程,工程构建完成以后,整个项目及其gradle配置文件如下:

默认情况下,Android studio使用cmake编译链工具,通过gradle脚本进行配置,默认cmake配置如下:

externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
        version "3.10.2"
    }
}

cmake文件和c++源代码都在src/main/cpp/目录下。

Android studio也支持ndk-buid,根据实际需求,我们也可以配置为ndk-build,当然,这需要我们先写好对应的Android.mkAppplication.mk(可选)配置文件,然后通过修改gradle配置中的externalNativeBuild配置项来进行更改。配置为nkd-build编译工具,则其配置文件如下:

externalNativeBuild {
    ndkBuild {
        path file('src/main/cpp/Android.mk')
    }
}

3. 使用现有android studio工程链接C++工程

当一个普通的不带C++本地库支持的项目需要引入一个现有的c++本地库时,可以使用android studio的Link C++ Psroject with Gradle功能来导入一个本地C++库,导入的库需要提供可用的cmake配置文件或Android.mk配置文件,导入工作是通过加载这些本地库编译配置文件来完成的。

4.在android studio配置javah工具

在Settings->Tools->External Tools下创建NDK group,在NDK group下创建javah工具。

详细的配置参数如下:

配置项参数
Programe$JDKPath$binjavah.exe
Arguments-classpath $ModuleFileDir$srcmainjava -jni -d $ModuleFileDir$srcmaincpp $FileClass$
Working directory$FileDir$

使用时,只需要在定义了native方法的java类上右键选择NDK->javah即可生成对应的c++本地函数定义的头文件。

截图中例子中,TestJni.java定义了本地函数add

package com.android.jnitest;

public class TestJni {
    public native int add(int a, int b);
}

使用javah生成的对应本地c++头文件com_android_jnitest_TestJni.h内容如下:

#include <jni.h>
/* Header for class com_android_jnitest_TestJni */

#ifndef _Included_com_android_jnitest_TestJni
#define _Included_com_android_jnitest_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_android_jnitest_TestJni
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_android_jnitest_TestJni_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

5. 工程配置

5.1 可选参数配置

可以在模块级 build.gradle 文件的 defaultConfig 块中配置另一个 externalNativeBuild 块,为 CMake 或 ndk-build 指定可选参数和标记 。

android {
    ...
    defaultConfig {
        ...
        // This block is different from the one you use to link Gradle
        // to your CMake or ndk-build script.
        externalNativeBuild {
          // For ndk-build, instead use the ndkBuild block.
          cmake {
            // Passes optional arguments to CMake.
            arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
            // Sets a flag to enable format macro constants for the C compiler.
            cFlags "-D__STDC_FORMAT_MACROS"
            // Sets optional flags for the C++ compiler.
            cppFlags "-fexceptions", "-frtti"
          }
        }
    }
    ...
}

通过上面的配置,可用对cmake的编译选项及C和C++编译选项做一些配置。

5.2 指定 ABI

默认情况下,Gradle 会针对 NDK 支持的应用二进制接口 (ABI) 将您的原生库编译到单独的 .so 文件中,并将这些文件全部打包到您的 APK 中。如果您希望 Gradle 仅编译和打包原生库的部分 ABI 配置,您可以在模块级文件 build.gradle 中使用 ndk.abiFilters 标记指定这些配置,如下所示:

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
        cmake {...}
        // or ndkBuild {...}
        }

        // Similar to other properties in the defaultConfig block,
        // you can configure the ndk block for each product flavor
        // in your build configuration.
        ndk {
        // Specifies the ABI configurations of your native
        // libraries Gradle should build and package with your APK.
        abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
        'arm64-v8a'
        }
    }
    buildTypes {...}
    externalNativeBuild {...}
}

6. 原生库支持

Android NDK 会提供一组随着新的 Android API 级别的后续发布而逐渐添加的原生标头和共享库文件。 请执行以下两个基本步骤的操作,以便让您的应用使用 NDK 提供的库:

  1. 在您的代码中添加与您想使用的库关联的标头。
  2. 通知编译系统您的原生模块需要在加载时链接库。

    • 如果您使用的是 ndk-build:将原生库添加到您 Android.mk 文件中的 LOCAL_LDLIBS 变量中。例如,要链接 /system/lib/libfoo.so,请添加以下这行代码:
    LOCAL_LDLIBS := -lfoo  

要列出多个库,请使用空格作为分隔符。

  • 如果您使用的是 CMake,向 CMake 编译脚本添加 find_library() 命令以找到 NDK 库并将其路径存储为一个变量。

    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 )
 然后再 CMake 脚本中的 [`target_link_libraries()`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) 命令来关联库: 
target_link_libraries( # Specifies the target library.
                           native-lib
                           # Links the log library to the target library.
                           ${log-lib} )

常用的android原生库包括以下一些:

API级别库名链接代码说明
3C 库 系统自动添加,无需配置
3动态链接器库LOCAL_LDLIBS := -ldl动态链接器的 dlopen(3) 和 dlsym(3) 功能
3Android日志库LOCAL_LDLIBS := -llog原生代码向 logcat 发送日志消息
3ZLib 压缩库LOCAL_LDLIBS := -lz
4OpenGL ES 1.xLOCAL_LDLIBS := -lGLESv1_CM
5OpenGL ES 2.0LOCAL_LDLIBS := -lGLESv2
8jnigraphicsLOCAL_LDLIBS += -ljnigraphics
9EGLLOCAL_LDLIBS += -lEGL分配和管理 OpenGLES 表面的原生平台接口
9OpenSL ESLOCAL_LDLIBS += -lOpenSLES原生音频处理库
9Android 原生应用apiLOCAL_LDLIBS += -landroid使用原生代码编写整个 Android 应用
14OpenMAX ALLOCAL_LDLIBS += -lOpenMAXAL原生多媒体处理库
14OpenSL ESLOCAL_LDLIBS += -lOpenSLES增加了 PCM 支持
18OpenGL ES 3.0LOCAL_LDLIBS := -lGLESv3
21OpenGL ES 3.1LOCAL_LDLIBS := -lGLESv3
24OpenGL ES 3.2LOCAL_LDLIBS := -lGLESv3

7. C++ 库支持

7.1 C++ 运行时库

NDK 支持多种 C++ 运行时库。

名称库文件功能
libc++共享库为 libc++_shared.so<br/>静态库为 libc++_static.aC++17 支持。
system/system/lib/libstdc++.sonewdelete。(在 r18 中已弃用。)
none 无头文件,有限 C++。

libc++

libc++ 同时提供静态库和共享库 。LLVM 的 libc++ 是 C++ 标准库,自 Lollipop 以来 Android 操作系统便一直使用该库,并且从 NDK r18 开始成为 NDK 中唯一可用的 STL。libc++ 的共享库为 libc++_shared.so,静态库为 libc++_static.a。

system

系统运行时指的是 /system/lib/libstdc++.so。请勿将该库与 GNU 的全功能 libstdc++ 混淆。在 Android 系统中,libstdc++ 只是 newdelete。对于全功能 C++ 标准库,请使用 libc++。

none

不包括STL。在这种情况下,没有关联或授权要求。不提供 C++ 标准头文件。

7.2 配置C++ 运行时

如果您要使用 CMake,则可使用模块级 build.gradle 文件中的 ANDROID_STL 变量,指定表表格中的一个运行时 。如果您要使用 ndk-build,则可使用 Application.mk 文件中的 APP_STL 变量指定表 1 中的一个运行时。

APP_STL := c++_shared

只能为应用选择一个运行时,并且只能在 Application.mk 中进行选择。

7.3 共享运行时

如果应用包括多个共享库,则应使用 libc++_shared.so

在 Android 系统中,NDK 使用的 libc++ 不是操作系统的一部分。这使得 NDK 用户能够获得最新的 libc++ 功能和问题修复程序,即使应用以旧版 Android 为目标。需要权衡的是,如果使用 libc++_shared.so,则必须将其纳入 APK 中。如果使用 Gradle 编译应用,则此步骤会自动完成。

7.4 C++ 异常

C++ 异常受 libc++ 支持,但其在 ndk-build 中默认为停用状态。这是因为之前 NDK 并不支持 C++ 异常。CMake 和独立工具链默认启用 C++ 异常。

要在 ndk-build 中针对整个应用启用异常,请将下面这一行代码添加至 Application.mk 文件:

APP_CPPFLAGS := -fexceptions

要针对单一 ndk-build 模块启用异常,请将下面这一行代码添加至相应模块的Android.mk中:

LOCAL_CPP_FEATURES := exceptions

或者,您可以使用:

LOCAL_CPPFLAGS := -fexceptions

7.4 RTTI

与异常一样,RTTI 也受 libc++ 支持,但在 ndk-build 中默认为停用状态。CMake 和独立工具链默认启用 RTTI。

要在 ndk-build 中针对整个应用启用 RTTI,请将下面这一行代码添加至 Application.mk文件:

APP_CPPFLAGS := -frtti    

要针对单一 ndk-build 模块启用 RTTI,请将下面这行代码添加至相应模块的 Android.mk中:

LOCAL_CPP_FEATURES := rtti    

或者,您可以使用:

LOCAL_CPPFLAGS := -frtti

参考:

将 Gradle 关联到您的原生库

向您的项目添加 C 和 C++ 代码

Android NDK 原生 API

文章评论已关闭!