JNI 初探----基本数据类型和开发配置

导读

Java Nativie Interface(JNI,中文名称Java本地接口)标准时Java平台的一部分,它允许Java代码和其他语言写得代码进行交互。JNI是本地编程接口,它使得Java虚拟机(VM)内部运行的Java代码能够用其他编程语言(如C、C++和汇编语言)编写的应用程序和库进行交互操作。JNI的主要用途是为了对硬件进行访问以及追求高效率或可重用C/C++库。
Android系统中采用了JNI的方式来调用C/C++方法,然而,在android系统里进一步加强了java JNI的使用,使JNI的调用更具有效率。因此,总的来说,Android系统里可以采用两种方式来使用JNI。第一种:Java原生JNI,使用dll等动态链接库 ;第二种,Android加强版JNI,通过动态加载*.so链接库来进行JNI调用。今天,我们分析第一种JNI使用方式,也称得上是JNI入门。

基本类型
由于Java与其他编程语言采用的语法不同,为了让Java与C/C++库函数能进行通信,约定的一个参数类型映射如下:
     字符        Java类型              C/C++类型       数组
      V          void                  void            
      Z          jboolean              boolean         [Z
      I          jint                  int             [I
      J          jlong                 long            [J
      D          jdouble               double          [D
      F          jfloat                float           [F
      B          jbyte                 jbyte           [B
      C          jchar                 char            [C
      S          jshort                shor            [S

上面的只是简单类型的一个映射,后面我们会完善其他参数类型的映射。

android studio开发配置
(一)ndk-build 配置

ndk-build配置是最初ndk推出时的一套配置方案,比较累赘,需要一对命令结合
1、android studio必须配置NDK,检查DNK配置方法:项目根目录->右键->Open module settings->SDK Location->Android NDK location

2、添加基本编译工具External Tools
File -> Settings -> Tools -> External Tools添加
(1)javah:主要用于在JNI开发的时,把java代码声明的JNI方法转化成C\C++头文件,以便进行JNI的C\C++端程序的开发。但是需要注意的是javah命令对Android编译生成的类文件并不能正常工作。如果对于Android的JNI要想生成C\C++头文件的话,可能只有先写个纯的java代码来进行JNI定义,接着用JDK编译,然后再用javah命令生成JNI的C\C++头文件。当然你也可以不用javah命令,直接手写JNI的C\C++头文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  用法:
javah [options] <classes>
其中, [options] 包括:
-o <file> 输出文件 (只能使用 -d 或 -o 之一)
-d <dir> 输出目录
-v -verbose 启用详细输出
-h --help -? 输出此消息
-version 输出版本信息
-jni 生成 JNI 样式的标头文件 (默认值)
-force 始终写入输出文件
-classpath <path> 从中加载类的路径
-cp <path> 从中加载类的路径
-bootclasspath <path> 从中加载引导类的路径
<classes> 是使用其全限定名称指定的
(例如, java.lang.Object)。

External Tools添加参数

1
2
3
4
5
6
Name:javah
Group:Exteral Tools
Description:javah
Program:$JDKPath$/bin/javah
Parameters:-classpath $Classpath$ -v -jni $FileClass$
Working directory:$SourcepathEntry$\..\jni

(2)ndk-build
External Tools添加参数

1
2
3
4
5
6
Name:ndk-build
Group:Exteral Tools
Description:ndk-build
Program:D:/sdk/sdk/ndk-bundle/ndk-build.cmd
Parameters:
Working directory:$ProjectFileDir$\app\src\main

(3)ndk-build clean
External Tools添加参数

1
2
3
4
5
6
Name:ndk-build clean
Group:Exteral Tools
Description:ndk-build clean
Program:D:/sdk/sdk/ndk-bundle/ndk-build.cmd
Parameters:clean
Working directory:$ProjectFileDir$\app\src\main

(4)javap:JDK自带的反汇编器,可以查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,从而了解很多编译器内部的工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置

External Tools添加参数

1
2
3
4
5
6
Name:javap
Group:Exteral Tools
Description:javap
Program:$JDKPath$/bin/javap
Parameters:-classpath $ModuleFileDir$/build/intermediates/classes/debug -s -c -verbose $FileClass$
Working directory:$ModuleFileDir$

(二)gradle-experimental 配置

gradle-experimental是google的一个实验性插件,主要用于NDK的开发与调试,全新的gradle-experimental插件使用了新的DSL语言,所以也需要用新的android插件com.android.model.application/com.android.model.library来替换老版中的com.android.application/com.android.library plugins,gradle-experimental的调试需要与LLDB结合是使用

1
2
3
dependencies {
classpath 'com.android.tools.build:gradle-experimental:0.7.0'
}

新的build.gradle配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
apply plugin: 'com.android.model.application'

model{
android {
compileSdkVersion = 23
buildToolsVersion = '23.0.3'

defaultConfig {
applicationId "com.ykq.demo.jniaes"
minSdkVersion.apiLevel = 16
targetSdkVersion.apiLevel = 23
}
/*
* native build settings: taking default for almost everything
*/

ndk {
moduleName = "mymodule"
ldLibs.addAll(['log'])
ldFlags.add("")
toolchain = "clang"
toolchainVersion = "3.9"
abiFilters.add("x86")
CFlags.add("")
cppFlags.add("")
debuggable = false
renderscriptNdkMode = false
stl = "system"
platformVersion = 15
}
buildTypes {
release {
minifyEnabled = false
proguardFiles.add(file('proguard-rules.txt'))
}
}
productFlavors {
// for detailed abiFilter descriptions, refer to "Supported ABIs" @
// https://developer.android.com/ndk/guides/abis.html#sa
create("arm") {
ndk.abiFilters.add("armeabi")
}
create("arm7") {
ndk.abiFilters.add("armeabi-v7a")
}
create("arm8") {
ndk.abiFilters.add("arm64-v8a")
}
create("x86") {
ndk.abiFilters.add("x86")
}
create("x86-64") {
ndk.abiFilters.add("x86_64")
}
create("mips") {
ndk.abiFilters.add("mips")
}
create("mips-64") {
ndk.abiFilters.add("mips64")
}
// To include all cpu architectures, leaves abiFilters empty
create("all")
}
// sources {
// main {
// jni {
// source {
// srcDir "src"
// }
// }
// }
// }
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
}
}

(三)CMake 配置

一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。如果您的原生源文件还没有CMake构建脚本,则您需要自行创建一个并包含适当的 CMake 命令。CMake 构建脚本是一个纯文本文件,您必须将其命名为CMakeLists.txt:
添加add_library

1
2
3
4
5
6
7
8
9
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 )
include_directories(src/main/cpp/include/)

add_library() 向您的 CMake 构建脚本添加源文件或库时,Android Studio 还会在您同步项目后在 Project 视图下显示关联的标头文件。不过,为了确保 CMake 可以在编译时定位您的标头文件,您需要将 include_directories() 命令添加到 CMake 构建脚本中并指定标头的路径:src/main/cpp/include/
添加 NDK API
Android NDK 提供了一套实用的原生 API 和库。通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,您可以使用这些 API 中的任意一种。

预构建的 NDK 库已经存在于 Android 平台上,因此,您无需再构建或将其打包到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,您甚至不需要在您的本地 NDK 安装中指定库的位置 - 只需要向 CMake 提供您希望使用的库的名称,并将其关联到您自己的原生库。

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

1
2
3
4
5
6
7
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() 命令关联库:

1
2
3
4
5
6
7
8
find_library(...)

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

NDK 还以源代码的形式包含一些库,您在构建和关联到您的原生库时需要使用这些代码。您可以使用 CMake 构建脚本中的 add_library() 命令,将源代码编译到原生库中。要提供本地 NDK 库的路径,您可以使用 ANDROID_NDK 路径变量,Android Studio 会自动为您定义此变量。

1
2
3
4
5
6
add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )

添加其他预构建库
添加预构建库与为 CMake 指定要构建的另一个原生库类似。不过,由于库已经预先构建,您需要使用 IMPORTED 标志告知 CMake 您只希望将库导入到项目中:

1
2
3
add_library( imported-lib
SHARED
IMPORTED )

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

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

1
2
3
4
5
6
7
8
9
add_library(...)
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() 命令,并包含标头文件的路径:

1
include_directories( imported-lib/include/ )

要将预构建库关联到您自己的原生库,请将其添加到 CMake 构建脚本的 target_link_libraries() 命令中:

1
target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

将 Gradle 关联到您的原生库
手动配置 Gradle
要手动配置 Gradle 以关联到您的原生库,您需要将 externalNativeBuild {} 块添加到模块级 build.gradle 文件中,并使用 cmake {} 或 ndkBuild {} 对其进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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 ndkBuild {}
cmake {

// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

// Sets optional flags for the C compiler.
cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"

// Sets a flag to enable format macro constants for the C++ compiler.
cppFlags "-D__STDC_FORMAT_MACROS"
}
}

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 {...}

productFlavors {
...
demo {
...
externalNativeBuild {
cmake {
...
// Specifies which native libraries to build and package for this
// product flavor. If you don't configure this property, Gradle
// builds and packages all shared object libraries that you define
// in your CMake or ndk-build project.
targets "native-lib-demo"
}
}
}

paid {
...
externalNativeBuild {
cmake {
...
targets "native-lib-paid"
}
}
}
}



// Encapsulates your external native build configurations.
externalNativeBuild {

// Encapsulates your CMake build configurations.
cmake {

// Provides a relative path to your CMake build script.
path "CMakeLists.txt"
}
}
}

如果您想要将 Gradle 关联到现有 ndk-build 项目,请使用 ndkBuild {} 块而不是 cmake {},并提供 Android.mk 文件的相对路径。如果 Application.mk 文件与您的 Android.mk 文件位于相同目录下,Gradle 也会包含此文件。

参考资料

1、Android Studio NDk调试(基于gradle-experimental插件与LLDB)
2、Android Studio使用gradle-experimental构建NDK工程(无需Android.mk、Application.mk文件)
3、向您的项目添加 C 和 C++ 代码
4、CMake

文章目录
  1. 1. 导读
  2. 2. 基本类型
  3. 3. android studio开发配置
    1. 3.1. (一)ndk-build 配置
    2. 3.2. (二)gradle-experimental 配置
    3. 3.3. (三)CMake 配置
  4. 4. 参考资料
,