经过上一篇《安卓JNI从0到1入门教程(一)》介绍,我们对JNI有了初步认识,接下来我会从ndk-build方式和cmake方式分别来介绍怎么构建native库:
一、ndk-build
ndk-build依赖配置文件Android.mk,存放代码的位置通常是jni目录
1.新建一个java测试类
package com.example.jni;
public class JNIDemo {
static {
//这个库名必须跟Android.mk的LOCAL_MODULE对应
System.loadLibrary("JniDemo");
}
public static native String test();
}
2.创建jni目录
3.生成.h文件
打开命令行,切换到项目的java目录,然后使用以下命令生成.h头文件。(没有javah命令的请先配置jdk环境变量)
javah -d ../jni com.example.jni.JNIDemo
生成的文件会在jni目录下出现
4.实现具体C++函数
在jni目录下新建一个.cpp文件,内容如下
#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.配置Android.mk和Application.mk
在jni目录下新建这两个文件,文件内容如下:
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
注意:Gradle 的 externalNativeBuild
会忽略 APP_ABI
。请在 splits
块内部使用 abiFilters
块或abi
块。
6.生成.so文件
你可以直接使用ndk-build命令构建,也可以在AndroidStudio中配置快捷方式。(本示例使用ndk版本为21.4.7075529)
(1)用构建命令构建
控制台切换到jni目录下,也就是包含Android.mk和Application.mk的目录,执行ndk-build命令,成功后可以在libs文件夹下找到。(没有ndk-build命令的需要先配置ndk环境变量)
(2)配置快捷工具
打开File-Settings-Tools-External Tools,添加新的工具,命名为ndk-build(随意命名),Program配置选择你的ndk所在的目录下的ndk-build.cmd,这个通常在你的AndroidStudio的安装目录下的ndk目录,Working directory填写项目的jni目录
点击执行ndk-build即可生成.so文件
7.在代码中使用native方法
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);
}
}
二、CMake
上面讲述了ndk-build,现在讲讲CMake怎么配置
1.添加CMakeLists.txt文件
在项目的app模块根目录新建CMakeLists.txt文件,填写如下内容:
#指定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)
示例中的add_library
函数用于指定要构建的本地库以及其源代码文件。target_link_libraries
函数用于指定要链接的系统库,例如log
库,它用于在本地代码中使用Android的日志功能。
最后,通过set_target_properties
函数,可以指定生成的共享库的输出路径,将其放置在libs/${ANDROID_ABI}
目录下,其中${ANDROID_ABI}
是一个CMake变量,表示当前目标平台的ABI(应用二进制接口)
更多CMakeLists.txt的配置项请参考配置Cmake
2.在app模块下的build.gradle中添加externalNativeBuild和ndk配置
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.添加C++文件
CMake通常使用cpp目录而不是jni目录,所以在src/main的目录下新建一个cpp目录,然后把.h和.cpp文件放进去
4.生成.so文件
使用AS的Rebuild Project构建项目,即可以在libs目录生成对应so文件。
好了,至此两种方式介绍已经讲完。
更多关于其中的配置可参考谷歌官方的文档。
后记
上述过程中,我们涉及了2个概念:动态库和静态库
在NDK编译过程中,这是两个不一样库文件生成方式。
1.动态库(Dynamic Library):
- 动态库是一种在运行时被加载和链接到应用程序中的库文件。在 Android NDK 中,动态库的文件扩展名通常为
.so
(Shared Object)。 - 动态库可以被多个应用程序或模块共享,以减少磁盘空间和内存占用。多个应用程序可以同时加载和使用同一个动态库。
- 动态库的加载和链接是在运行时进行的,这意味着库文件可以在应用程序运行期间动态加载和替换,从而实现更新和维护的方便性。
- 在 Android NDK 中,动态库使用 C/C++ 编译器生成,并使用
ndk-build
或 CMake 等构建工具进行编译和构建。
2.静态库(Static Library):
- 静态库是一种在编译时被链接到应用程序中的库文件。在 Android NDK 中,静态库的文件扩展名通常为
.a
(Archive)。 - 静态库会在应用程序编译时被复制到可执行文件中,每个使用了静态库的应用程序都包含了库的完整副本。
- 静态库的优点是可以提供独立的可执行文件,不需要依赖外部的库文件。它在性能上可能略优于动态库,因为静态库的函数调用是直接的,无需动态链接过程。
- 在 Android NDK 中,静态库也使用 C/C++ 编译器生成,并使用
ndk-build
或 CMake 等构建工具进行编译和构建。
在 Android NDK 开发中,你可以根据需求选择使用动态库或静态库。动态库适用于多个应用程序共享库文件、便于更新和维护的场景,而静态库适用于需要独立的可执行文件、性能优化和不需要依赖外部库的场景。