前几天去喜马拉雅面试,笔试中问到了这个问题。自己有印象,但好多细节没记住,悔的我哟!!!多年磨一剑,今天好好总结一下,也和大家分享一下自己的练习经验。毕竟自己脑子还是有点死,有时候只知其一,不知其二。希望大家批评指出,我们一起进步!
JNI的概念
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。它是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。另一方面,(个人认为)调用动态链接库的so文件来实现某些加密算法后能使得程序系统更加安全,不易破解。
JNI的实现步骤
1.配置NDK环境
2.新建JNI调用文件,并声明本地的JNI接口
打开Android Studio新建好一个Android项目后,我们新建一个JNI文件,在这里来声明和调用我们的JNI方法。我曾尝试将调用写在一个activity中,但是后来在编译,class文件的时候总是报错,说什么找不到activity吧。后来查阅网上也有解决办法,但是出于减少麻烦以及良好的习惯。我们自己建一个JNI的调用文件。然后在里面声明需要实现的方法例如:
public class JNI { public native int add(int a,int b); }
这个方法主要用于求两个数的和。
3.编译生成.class文件
点击菜单栏中的build-->rebuild后进行java文件的编辑可以生成相应的.class文件,生成的文件储存在\build\intermediates\classes\debug中。
![]()
4.编译.class生成.h文件
打开Android Studio自带的Terminal工具,进入build\intermediates\classes\debug>文件夹下,方便的办法是在project目录树中找到这个目录,然后将这个文件夹拖进Terminal编辑器。好了,接下来我们输入:
javah -jni [JNI.class文件的路径],我的是com.jerry.hometest.JNI。这里不用加文件的后缀名,路径分隔符是“.”而不是“/”。
紧接着在debug文件夹下会生成一个后缀名为.h的文件
![]()
5.新建JNIFolder
在项目中新建-->Folder-->JNIFolder,然后finish。会在/src/main/文件夹下生成一个jni文件夹。然后我们把刚刚生成的.h文件copy到这个文件夹下
![]()
6. 你的,h文件是这样的(红色字是参数,这是需要我们补上的。我们可以看见这里有四个参数:JNIEnv和JObject分别是JNI指针参数和JObject参数,这两个参数不是我们需要关心的,每个生成的.h文件应该都有这两个参数的。后面的两个参数类型正好就是我们在.class文件中传递进来的两个参数类型,但是具体的实现还不是在这里!
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jerry_hometest_JNI */
#ifndef _Included_com_jerry_hometest_JNI
#define _Included_com_jerry_hometest_JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jerry_hometest_JNI
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_jerry_hometest_JNI_add
(JNIEnv *env, jobject obj, jint a, jint b);
#ifdef __cplusplus
}
#endif
#endif
7.新建.c文件并实现JNI方法
在jni文件夹下新建一个.c文件,在这里实现我们定义的java方法,方法名直接从.h文件中copy,别忘了把“;”换成“{}”
#include "com_jerry_hometest_JNI.h"
JNIEXPORT jint JNICALL Java_com_jerry_hometest_JNI_add
(JNIEnv *env, jobject obj, jint a, jint b){
return a+b;}
8.实现了Java的方法后,我们还需要配置NDK环境,在jni文件夹中新建Android.mk文件,写入内容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= hello
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)
其中第三和第四个参数分别是定义的模块名称和运用到的.c文件,第四的参数必然是我们刚刚的.c文件。
9.再次打开Terminal编辑器,cd到jni文件夹下,输入ndk-build命令,生成.so文件
10.最后在JNI类中的static代码块中引入.so文件。
public class JNI {
static {
System.loadLibrary("hello");
}
public native int add(int a,int b);
}
引号里的应该就是.mk中的第三个参数吧,也可以根据智能提示把该文件加载进来。这样我们就可以在本地的Java文件中调用.c中的方法啦!有时候部分手机不能调到这个.so文件,也就是说在System.loadLibrary("hello");中报错。这是因为不同手机由于cpu的不同,使用不同的驱动。ABI:指应用基于哪种指令集来进行编译,ABI总共有四种,分别是armeabi、armeabi-v7a、mips、x86,它们都是表示cpu的类型。
我们在导入其它.so文件也是这么做的!
谢谢大家,希望对您有帮助!