After the introduction of the previous " Android JNI from 0 to 1 Getting Started Tutorial (1) ", we have a preliminary understanding of JNI. Next, I will introduce how to build native libraries from the ndk-build method and the cmake method:
This article uses the development environment:
Android Studio 4.0+
compileSDK 33
gradle 7.2.1
1. ndk-build
ndk-build depends on the configuration file Android.mk, and the location where the code is stored is usually the jni directory
1. Create a new java test class
package com.example.jni;
public class JNIDemo {
static {
//这个库名必须跟Android.mk的LOCAL_MODULE对应
System.loadLibrary("JniDemo");
}
public static native String test();
}
2. Create the jni directory
3. Generate .h file
Open the command line, switch to the java directory of the project, and use the following command to generate the .h header file. (If there is no javah command, please configure the jdk environment variable first)
javah -d ../jni com.example.jni.JNIDemo
The generated files will appear in the jni directory
4. Implement specific C++ functions
Create a new .cpp file in the jni directory with the following content
#include "com_example_jni_JNIDemo.h" //引入刚刚生成的头文件
extern "C"
//实现test函数实际功能
JNIEXPORT jstring JNICALL Java_com_example_jni_JNIDemo_test(JNIEnv * env, jclass clz){
return env->NewStringUTF("hello world");
}
5. Configure Android.mk and Application.mk
Create these two files in the jni directory. The contents of the files are as follows:
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#定义要构建生成的本地库的名称,以下示例会生成名为 libJniDemo.so 的库。
LOCAL_MODULE := JniDemo
#指定源代码文件
LOCAL_SRC_FILES := \
JNITest.cpp \
include $(BUILD_SHARED_LIBRARY)
Application.mk
#指定用于此应用的 C++ 标准库,默认情况下使用 system STL。其他选项包括 c++_shared、c++_static 和 none
APP_STL := c++_shared
APP_CPPFLAGS := -frtti -fexceptions
# APP_ABI:指定生成哪些cpu类型的so, all代表全部 常用的有:armeabi-v7a,arm64-v8a,x86,x86_64
APP_ABI := armeabi-v7a arm64-v8a
#APP_PLATFORM 会声明构建此应用所面向的 Android API 级别,并对应于应用的 minSdkVersion
APP_PLATFORM := android-21
Note : Gradle externalNativeBuild
will ignore APP_ABI
. Please use blocks or chunks inside ndk blocks (see configuration below).abiFilters
abi
6. Generate .so file
You can directly use the ndk-build command to build, or you can configure a shortcut in AndroidStudio. (This example uses ndk version 21.4.7075529)
(1) Build with the build command
Switch the console to the jni directory, that is, the directory containing Android.mk and Application.mk, execute the ndk-build command, and it can be found in the libs folder after success. (If there is no ndk-build command, you need to configure the ndk environment variable first)
(2) Configure shortcut tools
Open File-Settings-Tools-External Tools, add a new tool, name it ndk-build (name it at will), and select ndk-build.cmd in the directory where your ndk is located in the Program configuration, which is usually installed in your AndroidStudio The ndk directory under the directory, the Working directory fills in the jni directory of the project
Click to execute ndk-build to generate the .so file
7. Use native methods in code
public class MainActivity extends AppCompatActivity {
private TextView tvMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvMsg = findViewById(R.id.tv_msg);
//调用native方法
String msg = JNIDemo.test();
tvMsg.setText(msg);
}
}
If you are using an older version of Gradle, you need to add the following configuration in the build.gradle of the app module to link the so library (the generated so library needs to be placed in the libs directory):
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
//...其他省略
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
//...其他省略
}
2. CMake
The above describes ndk-build, now let’s talk about how to configure CMake
1. Add the CMakeLists.txt file
Create a new CMakeLists.txt file in the root directory of the app module of the project, and fill in the following content:
#指定CMake的最低版本要求
cmake_minimum_required(VERSION 3.18.1)
# 定义本地库的名称
set(my_lib_name JniDemo)
#添加库配置,如果有多个库,可以添加多个add_library方法
add_library( # 指定生成库的名称,使用上面定义的变量
${my_lib_name}
# 标识是静态库还是动态库 STATIC:静态库 SHARED:动态库
SHARED
# C/C++源代码文件路径
src/main/cpp/JNITest.cpp)
#指定.h头文件的目录
include_directories(src/main/cpp/)
# 指定构建输出路径
set_target_properties(${my_lib_name} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")
# 链接外部静态库(如果你的静态库或动态库依赖于其他库或依赖项)
target_link_libraries(MyStaticLibrary PRIVATE MyExternalStaticLibrary)
The functions in the examples add_library
are used to specify the native library to build and its source code files. target_link_libraries
The function is used to specify the system library to be linked, such as log
the library, which is used to use Android's logging function in native code.
Finally, through set_target_properties
the function, you can specify the output path of the generated shared library and place it in libs/${ANDROID_ABI}
the directory, which ${ANDROID_ABI}
is a CMake variable that represents the ABI (Application Binary Interface) of the current target platform
For more configuration items of CMakeLists.txt, please refer to Configuring Cmake
2. Add externalNativeBuild and ndk configuration in build.gradle under the app module
android {
compileSdk 33
defaultConfig {
applicationId "com.example.jni"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
//指定编译的ABI平台
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {
//指定CMakeLists文件所在位置
path "CMakeLists.txt"
}
}
//...省略
}
3. Add C++ files
CMake usually uses the cpp directory instead of the jni directory, so create a new cpp directory under the src/main directory, and then put the .h and .cpp files into it
4. Generate .so file
Use the Rebuild Project of AS to build the project, that is, the corresponding so file can be generated in the libs directory.
Well, so far the introduction of the two methods has been finished.
For more information about the configuration, please refer to Google's official documentation.
postscript
In the above process, we involved two concepts: dynamic library and static library
During the NDK compilation process, these are two different library file generation methods.
1. Dynamic Library (Dynamic Library):
- A dynamic library is a library file that is loaded and linked into an application at runtime. In Android NDK, the file extension of dynamic library is usually
.so
(Shared Object). - Dynamic libraries can be shared by multiple applications or modules to reduce disk space and memory usage. Multiple applications can load and use the same dynamic library at the same time.
- The loading and linking of the dynamic library is carried out at runtime, which means that the library files can be dynamically loaded and replaced during the running of the application, thereby realizing the convenience of updating and maintenance.
- In Android NDK, dynamic libraries are generated using C/C++ compilers, and
ndk-build
compiled and built using build tools such as CMake or CMake.
2. Static Library:
- A static library is a library file that is linked into an application at compile time. In the Android NDK, the file extension of the static library is usually
.a
(Archive). - Static libraries are copied into the executable when the application is compiled, and every application that uses a static library includes a complete copy of the library.
- The advantage of static libraries is that they can provide independent executable files without relying on external library files. It may be slightly better than a dynamic library in performance, because the function call of the static library is direct, without the dynamic link process.
- In Android NDK, static libraries are also generated using C/C++ compilers, and
ndk-build
compiled and built using build tools such as CMake or CMake.
In Android NDK development, you can choose to use dynamic libraries or static libraries according to your needs. Dynamic libraries are suitable for scenarios where multiple applications share library files for easy update and maintenance, while static libraries are suitable for scenarios that require independent executable files, performance optimization, and do not need to rely on external libraries.