目录

Android Studio上使用JNI

题记

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

版本信息

步骤

Step 1. 下载NDK

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

NDK下载

下载之后解压,并将解压后的目录加入环境变量,我的路径是G:\android-ndk-r21

在终端输入 ndk-build 验证一下配置是否成功:

ndk-build

Step 2. 新建项目

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

image-20200604135729797

Step 3. AS环境配置

配置NDK路径

Ctrl+Alt+Shift+S打开配置,设定NDK路径:

设定NDK路径

配置build.gradle文件

打开app文件夹中的build.gradle文件,在defaultConfig中输入以下代码,其中moduleName与最后生成的.so文件的文件名有关,即lib[moduleName].so,且Java源文件中loadLibrary方法的参数就是这个moduleName的名字。abiFilters表示CPU的型号,不过好像可以删掉,不删的话反而在调试的时候可能手机型号不支持。

1
2
3
4
ndk{
    moduleName "SEnhanceNDK"
    // abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}

同样在这个build.gradle文件中,在buildTypes下输入以下代码。

1
2
3
4
5
6
7
        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.

build.gradle

新建Android.mk和Application.mk

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

image-20200627144804323

在jni目录下新建Android.mk和Application.mk,输入内容如下:

Android.mk:

1
2
3
4
5
6
7
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:

1
APP_ABI := all

image-20200627144503239

Step 4. 编写JNI文件,并生成.so文件

1. 创建JAVA源文件,编写 native 方法

创建JAVA源文件

如图示创建一个java文件,写入代码(注意修改代码中package的名称),然后复制这个文件的路径,命令行切换到main/java目录中.

输入命令javah -d ../jni com.xxx.xxx.JNI,其中com.xxx.xxx.JNI需要替换成你复制的路径。

p.s. 新版本的jdk没有javah的命令,有资料说可以用javac -h这种命令,但是我尝试之后并没有解决,所以我的建议是直接换成jdk8,而且安装也很简单

image-20200604145724280

命令执行完之后,就会生成一个.h文件,在同级目录创建一个名字相同的.c文件,输入以下代码用于测试,注意函数名要和.h文件的函数名一致:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#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命令。

ndk-build

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

libs

方法二. AS菜单栏Build->Rebuild Project

Rebuild这个方法好像不太行,不确定问题在哪里,可能与build.gradle文件jniLibs.srcDirs或者jni.srcDirs的配置有关。

对build.gradle文件进行修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        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并执行程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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);
    }
}

结果

结果

参考资料

其他事项

Android.mk添加目录中所有源文件

ref. Android.mk添加目录中所有源文件

使用场景:com_whu_jniproj_JNI.c中include了cljtest.h,并且使用了cljtest中的函数。

报错:

  1. 如果Android.mk为 LOCAL_SRC_FILES := com_whu_jniproj_JNI.c时,Build就会报错:
1
2
3
4
5
6
7
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
  1. 如果Android.mk为 LOCAL_SRC_FILES := cljtest.c时,Build通过,报错:

报错

  1. 如果Android.mk为 LOCAL_SRC_FILES := cljtest.c com_whu_jniproj_JNI.c时,不报错。

调试方法,在android.mk中使用下列代码,然后在终端中使用ndk-build编译:

1
2
$(warning "the value of LOCAL_PATH is $(LOCAL_PATH)")
$(warning "the value of LOCAL_SRC_FILES is $(LOCAL_SRC_FILES)")

由于ref中的其他方案都报错,只能使用下面这种,注意这个方法不会递归找.c文件,是单目录的,不过也比一个一个加方便多了:

1
2
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.c)
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)