Android Studio上使用JNI
题记
做了一个C程序,需要移植到安卓上,移植到安卓上就需要使用JNI(Java Native Interface),这里做一个Android Studio(AS)上使用JNI的记录。版本和环境信息如下,不同版本的AS操作有较大区别,也只能踩坑试试才知道行不行:

步骤
Step 1. 下载NDK
NDK(Native Developer Kit)是谷歌给开发人员的工具包,感觉主要功能就是能够更容易使用JNI的工具。

下载之后解压,并将解压后的目录加入环境变量,我的路径是G:\android-ndk-r21。
在终端输入 ndk-build 验证一下配置是否成功:

Step 2. 新建项目
新建一个项目,不需要勾选Include C++ support.,新建之后调整成Project视图。

Step 3. AS环境配置
配置NDK路径
Ctrl+Alt+Shift+S打开配置,设定NDK路径:

配置build.gradle文件
打开app文件夹中的build.gradle文件,在defaultConfig中输入以下代码,其中moduleName与最后生成的.so文件的文件名有关,即lib[moduleName].so,且Java源文件中loadLibrary方法的参数就是这个moduleName的名字。abiFilters表示CPU的型号,不过好像可以删掉,不删的话反而在调试的时候可能手机型号不支持。
ndk{
moduleName "SEnhanceNDK"
// abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}同样在这个build.gradle文件中,在buildTypes下输入以下代码。
sourceSets {
main() {
// jniLibs.srcDirs = ['src/main/libs']
jniLibs.srcDirs = []
jni.srcDirs = [] //屏蔽掉默认的jni编译生成过程
}
}ref. Error: Your project contains C++ files but it is not using a supported native build system.

新建Android.mk和Application.mk
跳过这步在后续步骤中报的错(图示第一次输入命令ndk-build是没有这两个.mk文件的情况):

在jni目录下新建Android.mk和Application.mk,输入内容如下:
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := myNDK # moduleName
LOCAL_SRC_FILES := com_whu_jniproj_JNI.c # 下一步中c文件名字
include $(BUILD_SHARED_LIBRARY)Application.mk:
APP_ABI := all

Step 4. 编写JNI文件,并生成.so文件
1. 创建JAVA源文件,编写 native 方法

如图示创建一个java文件,写入代码(注意修改代码中package的名称),然后复制这个文件的路径,命令行切换到main/java目录中.
输入命令javah -d ../jni com.xxx.xxx.JNI,其中com.xxx.xxx.JNI需要替换成你复制的路径。
p.s. 新版本的jdk没有javah的命令,有资料说可以用javac -h这种命令,但是我尝试之后并没有解决,所以我的建议是直接换成jdk8,而且安装也很简单

命令执行完之后,就会生成一个.h文件,在同级目录创建一个名字相同的.c文件,输入以下代码用于测试,注意函数名要和.h文件的函数名一致:
#include "com_whu_senhance_JNI.h"
char* gettext(){
return "Hi";
}
/**
jstring: 返回值
Java_全类名_方法名
JNIEnv* env: 里面有很多方法
jobject jobj:谁调用这个方法就是谁;即JNI.this
*/
jstring Java_com_whu_senhance_JNI_sayHello(JNIEnv* env, jobject jobj){
//jstring (*NewStringUTF)(JNIEnv*, const char*);
char* text = "I am from C";
text = gettext();
return (*env)->NewStringUTF(env,text);
}2. 生成.so文件
方法一. 在jni目录中,执行ndk-build命令。

执行结束后会生成libs和obj文件夹,里面就是.so文件,需要创建一个名为jniLibs的文件夹,然后把libs中的内容全部复制进这个文件夹里面,才能使用.so文件。

方法二. AS菜单栏Build->Rebuild Project
Rebuild这个方法好像不太行,不确定问题在哪里,可能与build.gradle文件jniLibs.srcDirs或者jni.srcDirs的配置有关。
对build.gradle文件进行修改:
sourceSets {
main() {
// jniLibs.srcDirs = ['src/main/libs']
jniLibs.srcDirs = []
jni.srcDirs = ['src/main/jni']
}
}
externalNativeBuild{
ndkBuild{
path file('src/main/jni/Android.mk')
}
}然后rebuild,生成.so文件在app/build/intermediates/ndkBuild/debug/obj/local(实际上还有其他2个目录也生成了.so文件"D:\AndroidStudioProjects\JNIProj\app\build\intermediates\transforms\stripDebugSymbol\debug\0\lib\x86\libmyNDK.so"和"D:\AndroidStudioProjects\JNIProj\app\build\intermediates\transforms\mergeJniLibs\debug\0\lib\x86\libmyNDK.so")路径中(看参考资料,可能有的在intermediates/ndk路径里面,我这里没有ndk文件夹生成)。
与方法一不同,使用这个方式不需要复制.so的位置,可能默认的jnilib就是这个路径。
Step 5. 修改MainActivity并执行程序
package com.whu.senhance;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String result = new JNI().sayHello();
System.out.println("result = "+result);
}
}结果

参考资料
- JNI 开发详细初体验(Mac Android Studio 3.1+) - 掘金 https://juejin.im/post/5b90a7c1e51d450e697311de
- 尚硅谷Android视频教程《JNI》【注:AS版本很老】 https://www.bilibili.com/video/BV1qW411L7oY?p=35
- Android+真机调试 https://www.cnblogs.com/xqz0618/p/12725515.html
- Android之JNI实现函数中LOG打印 【注:LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog要写在include $(CLEAR_VARS)的后面】https://blog.csdn.net/jjunjoe/article/details/7020236
其他事项
Android.mk添加目录中所有源文件
ref. Android.mk添加目录中所有源文件
使用场景:com_whu_jniproj_JNI.c中include了cljtest.h,并且使用了cljtest中的函数。
报错:
- 如果Android.mk为 LOCAL_SRC_FILES := com_whu_jniproj_JNI.c时,Build就会报错:
Build command failed.
Error while executing process G:\android-ndk-r21\ndk-build.cmd with arguments {NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=D:\AndroidStudioProjects\JNIProj\app\src\main\jni\Android.mk NDK_APPLICATION_MK=D:\AndroidStudioProjects\JNIProj\app\src\main\jni\Application.mk APP_ABI=x86_64 NDK_ALL_ABIS=x86_64 NDK_DEBUG=1 APP_PLATFORM=android-21 NDK_OUT=D:/AndroidStudioProjects/JNIProj/app/build/intermediates/ndkBuild/debug/obj NDK_LIBS_OUT=D:\AndroidStudioProjects\JNIProj\app\build\intermediates\ndkBuild\debug\lib D:/AndroidStudioProjects/JNIProj/app/build/intermediates/ndkBuild/debug/obj/local/x86_64/libmyNDK.so}
[x86_64] Compile : myNDK <= com_whu_jniproj_JNI.c
[x86_64] SharedLibrary : libmyNDK.so
D:/AndroidStudioProjects/JNIProj/app/src/main/jni/com_whu_jniproj_JNI.c:17: error: undefined reference to 'gettesttext'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [G:/android-ndk-r21/build//../build/core/build-binary.mk:725: D:/AndroidStudioProjects/JNIProj/app/build/intermediates/ndkBuild/debug/obj/local/x86_64/libmyNDK.so] Error 1- 如果Android.mk为 LOCAL_SRC_FILES := cljtest.c时,Build通过,报错:

- 如果Android.mk为 LOCAL_SRC_FILES := cljtest.c com_whu_jniproj_JNI.c时,不报错。
调试方法,在android.mk中使用下列代码,然后在终端中使用ndk-build编译:
$(warning "the value of LOCAL_PATH is $(LOCAL_PATH)")
$(warning "the value of LOCAL_SRC_FILES is $(LOCAL_SRC_FILES)")由于ref中的其他方案都报错,只能使用下面这种,注意这个方法不会递归找.c文件,是单目录的,不过也比一个一个加方便多了:
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.c)
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)