Android NDK 编译工具ndk-build的使用
1.创建native相关方法
新建 JNIUtils.java
,并创建native相关方法
public class JNIUtils {
/**
* 通过JNI从C中获取数据
*
* @return String
*/
public static native String getDataFromC();
/**
* 通过JNI从C++中获取数据
*
* @return String
*/
public static native String getDataFromCPP();
}
1.1 native相关方法去掉报红
取消检测即可,打开 Settings>Editor>Inspections>Android>Missing JNI function 去掉勾选。
去掉后,效果如下:
2.创建c/c++文件
2.1 生成头文件
- Terminal终端,通过下面命令 切换到
项目xx\app\
目录下。
cd D:\Brainbg\AndroidNotes\NDK-First\app
注:由于下面的路径都比较长,我们可以右击相应的文件目录进行快捷复制:
- 根据java文件生成c的头文件, 执行如下命令
格式:
javah d jni -encoding utf-8 classpath /java文件夹路径 包名+类名
javah d jni classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils
或者
javah d jni -encoding utf-8 classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils
其中
- javah :生成头文件
- d jni :当前目录下创建jni文件夹
- -encoding utf-8 :指定编码格式为utf-8
- classpath D:\Workspace\NDKFirst\app\src\main\java\ :到java目录的路径
- com.brainbg.ndkfirst.JNIUtils :包名+类名
注:不加上-encoding utf-8
,可能会提示错误: 编码GBK的不可映射字符
。
- 执行后,收缩app目录后重新打开,会发现多了一个jni的目录,
com_brainbg_ndkfirst_JNIUtils.h
就是新生成的头文件。
com_brainbg_ndkfirst_JNIUtils.h
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_brainbg_ndkfirst_JNIUtils */
#ifndef _Included_com_brainbg_ndkfirst_JNIUtils
#define _Included_com_brainbg_ndkfirst_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_brainbg_ndkfirst_JNIUtils
* Method: getDataFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC
(JNIEnv *, jclass);
/*
* Class: com_brainbg_ndkfirst_JNIUtils
* Method: getDataFromCPP
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
其中,里面的2个方法Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC
、JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP
是重点,后面需要直接复制进C/C++的文件中进行修改,同时,我们也能发现它生成的格式:
格式:Java_包名_类名_方法名
2.2 添加 c/c++文件
在jni文件夹中添加相应的文件,同时复制上面头文件生成的方法修改。
- 添加c文件
firstc.c
#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"
JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is my first jni from C!");
}
- 添加cpp文件
firstcpp.cpp
#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"
JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP(JNIEnv *env, jclass jclass) {
return env->NewStringUTF("This is my first jni from CPP!");
}
修改好的内容后,你会留意到上面还有提示:大意就是目前的c/c++文件还不属于项目中的一部分!为此,我们还需要处理build.gradle、Android.mk等文件。
3.添加mk文件
3.1 添加 Android.mk
文件(必加)
注意mk文件里面不能添加注释,不然编译不通过。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := first-jni
LOCAL_SRC_FILES := firstc.c firstcpp.cpp
include $(BUILD_SHARED_LIBRARY)
- LOCAL_PATH := $(call my-dir):此变量表示源文件在开发树中的位置。在这行代码中,编译系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件本身所在的目录)的路径。
- include $(CLEAR_VARS):其值由编译系统提供。CLEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。请注意,GNU Makefile 不会清除 LOCAL_PATH。此变量必须保留其值,因为系统在单一 GNU Make 执行环境(其中的所有变量都是全局变量)中解析所有编译控制文件。在描述每个模块之前,必须声明(重新声明)此变量。
- LOCAL_MODULE:LOCAL_MODULE 变量存储您要编译的模块的名称。请在应用的每个模块中使用一次此变量。每个模块名称必须唯一,且不含任何空格。编译系统在生成最终共享库文件时,会对您分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 libfirst-jni.so 的库。
- LOCAL_SRC_FILES:
多个文件以空格分隔开
,变量必须包含要编译到模块中的 C 和/或 C++ 源文件列表。 - nclude $(BUILD_SHARED_LIBRARY):帮助系统将所有内容连接到一起,变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要编译的内容以及编译方式。
注意:LOCAL_MODULE中,如果模块名称的开头已经是 lib,则编译系统不会附加额外的 lib 前缀;而是按原样采用模块名称,并添加 .so 扩展名。因此,比如原来名为 libfoo.c 的源文件仍会生成名为 libfoo.so 的共享对象文件。此行为是为了支持 Android 平台源文件根据 Android.mk 文件生成的库;所有这些库的名称都以 lib 开头。
更多内容,可以直接查看:Android.mk 官方介绍
3.2 添加 Application.mk
文件(可选)
APP_PLATFORM := android-16
APP_ABI :=all
- APP_PLATFORM :指定so库所支持最低的API,即 会声明编译此应用所面向的 Android API 级别,并对应于应用的
minSdkVersion
注: 如果未指定,ndk-build 将以 NDK 支持的最低 API 级别为目标。最新 NDK 支持的最低 API 级别总是足够低,可以支持几乎所有使用中的设备。
警告:将 APP_PLATFORM 设置为高于应用的 minSdkVersion 可能会生成一个无法在旧设备上运行的应用。在大多数情况下,库将无法加载,因为它们引用了在旧设备上不可用的符号。
- APP_ABI:指定生成平台的so库
默认情况下,NDK 编译系统会为所有非弃用 ABI 生成代码。您可以使用 APP_ABI 设置为特定 ABI 生成代码。
指令集 | 值 |
---|---|
32 位 ARMv7 | APP_ABI := armeabi-v7a |
64 位 ARMv8 (AArch64) | APP_ABI := arm64-v8a |
x86 | APP_ABI := x86 |
x86-64 | APP_ABI := x86_64 |
所有支持的 ABI(默认) | APP_ABI := all |
ABI 和支持的指令集
,可以查看Google的ABI 管理,其中有相关内容如下:
注:不添加Application.mk
,会提示Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
更多内容,可以直接查看:Application.mk 官方介绍
4.编译so库文件
进入app目录,执行ndk-build
进行编译
cd D:\Workspace\NDKFirst\app
ndk-build
执行成功后,效果如下
同时项目中会得到相应的so包,其中lib为核心,obj为编译中产生的文件,可删除。
4.用Gradle链接c++项目
- jni目录中右击任意文件选择
Link C++ project with Gradle
- 其中
Build System
选择ndk-build ,Project Path
选择Android.mk的路径,而后确认。
- 完成上面的操作后,app/build.gradle里面会出现如下代码
- build.gradle
android {
......
externalNativeBuild {
ndkBuild {
path file('jni/Android.mk')
}
}
}
当然,下次项目的话,我们也可以直接加入上面代码即可。
6.加载so库、运行app
- JNIUtils
public class JNIUtils {
public static final String TAG = JNIUtils.class.getSimpleName();
static {
try {
System.loadLibrary("first-jni"); //加载so库
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
Log.e(TAG, "loadLibrary fail !");
}
}
/**
* 通过JNI从C中获取数据
*
* @return String
*/
public static native String getDataFromC();
/**
* 通过JNI从C++中获取数据
*
* @return String
*/
public static native String getDataFromCPP();
}
- MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvC = findViewById(R.id.tv_c);
TextView tvCPP = findViewById(R.id.tv_cpp);
tvC.setText(JNIUtils.getDataFromC());
tvCPP.setText(JNIUtils.getDataFromCPP());
}
}
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="c-content" />
<TextView
android:id="@+id/tv_cpp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="cpp-content" />
</LinearLayout>
- 运行后效果
到此为止,第一个关于NDK、JNI的Demo已经完成,相关文章,后续可能、应该、大概也会推出吧。
7.下载地址
参考资料
https://developer.android.google.cn/ndk/guides
https://blog.youkuaiyun.com/young_time/article/details/80346631
https://yq.aliyun.com/articles/60710?spm=a2c4e.11153940.0.0.11bc68d9CLrDix
https://www.jianshu.com/p/87ce6f565d37
作者:Brainbg(白雨)
GitHub:https://github.com/Brainbg
博客:https://www.brainbg.com/
简书:https://www.jianshu.com/u/94518ede7100
优快云:https://blog.youkuaiyun.com/u014720022