一、JNI简介
JNI(Java Native interface)又称java本地接口,相当于java和C之间互相调用的媒介,我们这里一般是java调用C或者C++代码,为什么需要使用JNI呢,因为很多功能的处理,java做的并没有C++做的好,而C++在这些方面都有现成的例子,所以我们可以通过调用C代码去实现更好的一个功能,使用JNI技术,其实就是在Java程序中,调用C语言的函数库中提供的函数,来完成一些Java语言无法完成的任务。由于Java语言和C语言结构完全不相同,因此若想让它们二者交互,则需要制定一系列的规范,JNI就是这组规范,此时Java只和JNI交互,而由JNI去和C语言交互。
1>Java语言提供的类库无法满足要求,且在数学运算,实时渲染的游戏上,音视频处理等方面上与C/C++相比效率稍低。
2>Java语言无法直接操作硬件,C/C++代码不仅能操作硬件而且还能发挥硬件最佳性能。
3>使用Java调用本地的C/C++代码所写的库,省去了重复开发的麻烦,并且可以利用很多开源的库提高程序效率。
JNI技术分为两部分:Java端和C语言端。且以Java端为主导。
1>Java程序员在Java端定义一些native方法,并将这些方法以C语言头文件的方式提供给C程序员。
2>C程序员使用C语言,来实现Java程序员提供的头文件中定义的函数。
3> 接着,C程序员将函数打包成一个库文件,并将库文件交给Java程序员。
4>Java程序员在Java程序中导入库文件,然后调用native方法。System.loadLibrary(“media_jni”);
二、NDK简介
NDK全称:Native Development Kit ,NDK是一系列工具的集合,它有很多作用。首先,NDK可以帮助开发者快速开发C(或C++)的动态库。其次,NDK集成了交叉编译器。使用NDK,我们可以将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。NDK工具必须在Linux下运行,它可以在linux环境下编译出可以在arm平台下运行的二进制库文件,在Java程序执行的时候,若在某个类中调用了native方法,则虚拟机会通过JNI来转调用库文件中的C语言代码。提示:C代码最终是在Linux进程中执行的,而不是在虚拟机中。
三、JNI在AndroidStudio中环境的配置
首先,在项目的设置中下载NDK所需要的文件,如图:
然后在local.properties中配置SDK和NDK的引用路径,这里一般是默认设置;
ndk.dir=C\:\\Users\\shiqi\\AppData\\Local\\Android\\Sdk\\ndk-bundle
sdk.dir=C\:\\Users\\shiqi\\AppData\\Local\\Android\\Sdk
然后,在项目的gradle.properties中最后一行配置ndk的使用
android.useDeprecatedNdk=true
然后,在app的build.gradle中配置ndk的moduleName和sourceSets,moduleName是指所编译的.so库的名称,记住,几个前后引用的库的名称一定要相同,否则会报错
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.example.jc.myjni"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk{
moduleName "MyFirst"
}
sourceSets.main{
jni.srcDirs = []
jniLibs.srcDir "src/main/libs"
//这里是编译完成后存放so文件的;路径,个人喜欢放在main路径下,比较直观
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
四、新建C代码配置环境并且编译so
新建一个类封装调用jni的方法,方法以native命名,System.loadLibrary(“MyFirst”)表示这里将引用MyFirst库,这里和gradle里设置的库名称相同
public class MyNdk {
static {
System.loadLibrary("MyFirst");
}
public native String getString();
}
配置NDK的环境变量,因为我们这里要编译java文件生成头文件,使用Javah命令和ndk-build命令,在Terminal中进行,所以需要配置,在环境变量中的path中编辑添加ndk的路径;
在Terminal命令行下,通过cd命令进去java文件下下,编译Java文件以包名.类名的形式去编译,调用Javah命令,最终生成头文件
新建一个JNI目录,存放C代码
在jni目录中新建一个cpp文件,对应头文件中的代码,将头文件代码复制过来,然后自己实现一个简单的调用,返回字符串;
#include "com_example_jc_myjni_MyNdk.h"
extern "C"
JNIEXPORT jstring JNICALL Java_com_example_jc_myjni_MyNdk_getString
(JNIEnv * env, jobject obj){
return env->NewStringUTF("mmfrom C++");
}
然后我们需要配置Android.mk文件和Application.mk文件,在jni目录下新建两个文件Android.mk文件和Application.mk文件,请记住,mk文件的格式一定要正确,否则就会出现各种编译问题,这个在后面说;Application中的APP_MODULES同样也是只调用的so库,与gradle里一样同名,APP_PLATFORM是指兼容的最低版本
Android.mk
==========
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyFirst
LOCAL_SRC_FILES :=MyLibrary.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
==========
APP_MODULES := MyFirst
APP_ABI := all
APP_PLATFORM := android-14
然后,在主界面去调用jni函数
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(new MyNdk().getString());
}
}
执行最关键的一步,编译文件,生成so文件,通过cd命令进入main\jni文件夹下,调用ndk-build
C:\Users\shiqi\Desktop\MyJNI\MyJNI\app\src\main\java>cd..
C:\Users\shiqi\Desktop\MyJNI\MyJNI\app\src\main>cd jni
C:\Users\shiqi\Desktop\MyJNI\MyJNI\app\src\main\jni>ndk-build
[arm64-v8a] Compile++ : MyFirst <= MyLibrary.cpp
[arm64-v8a] SharedLibrary : libMyFirst.so
[arm64-v8a] Install : libMyFirst.so => libs/arm64-v8a/libMyFirst.so
[x86_64] Compile++ : MyFirst <= MyLibrary.cpp
[x86_64] SharedLibrary : libMyFirst.so
[x86_64] Install : libMyFirst.so => libs/x86_64/libMyFirst.so
[mips64] Compile++ : MyFirst <= MyLibrary.cpp
[mips64] SharedLibrary : libMyFirst.so
[mips64] Install : libMyFirst.so => libs/mips64/libMyFirst.so
[armeabi-v7a] Compile++ thumb: MyFirst <= MyLibrary.cpp
[armeabi-v7a] StaticLibrary : libstdc++.a
[armeabi-v7a] SharedLibrary : libMyFirst.so
[armeabi-v7a] Install : libMyFirst.so => libs/armeabi-v7a/libMyFirst.so
到这里一个简单的流程就成功了,运行的结果便是jni的返回的字符串显示,下面看下编译成功的libs,如果编译失败,libs里面不会有so文件;
接下来我们来看看怎么调用现成的C代码呢,在JNI中,直接去调用c代码是不行的,需要我们通过ndk去编译成so文件后,在jni方法中调用即可,这里我们把代码及头文件粘贴到jni目录,然后在Android.mk文件中配置编译的C文件信息,如下,注意,LOCAL_SRC_FILES里面的每个cpp或者c文件后面是有空格的,\是换行,如果没有空格,系统就无法辨认这是几个文件,会一直报错,博主之前没搞过mk没注意就被坑了,如果编译出现问题,试试删除obj文件夹后再重新编译,可能因为之前的编译记录导致的问题,重新再命令行中使用ndk-build命令,编译生成新的so文件;
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyFirst
LOCAL_SRC_FILES :=MyLibrary.cpp \
functions.c \
helpers.c \
images.c \
menu.c \
main.c \
crc16fast.c \
LEPTON_AGC.c \
LEPTON_I2C_Protocol.c \
LEPTON_I2C_Service.c \
LEPTON_SDK.c \
LEPTON_SYS.c \
LEPTON_VID.c \
raspi_I2C.c
include $(BUILD_SHARED_LIBRARY)
然后在jni方法中调用c的方法,引用其头文件即可
#include "com_example_jc_myjni_MyNdk.h"
//引用C代码的头文件
#include "LEPTON_SDK.h"
#include "LEPTON_SYS.h"
#include "LEPTON_Types.h"
LEP_CAMERA_PORT_DESC_T _port;
extern "C"
JNIEXPORT jstring JNICALL Java_com_example_jc_myjni_MyNdk_getString
(JNIEnv * env, jobject obj){
//调用C代码
LEP_OpenPort(1, LEP_CCI_TWI, 400, &_port);
LEP_RunSysFFCNormalization(&_port);
return env->NewStringUTF("mmfrom C++");
}
五、头文件
1>头文件中存放的是对某个库中所定义的函数、宏、类型、全局变量等进行声明,它类似于一份仓库清单。若用户程序中需要使用某个库中的函数,则只需要将该库所对应的头文件include到程序中即可。
2>头文件中定义的是库中所有函数的函数原型。而函数的具体实现则是在库文件中。
3>头文件是给编译器用的,库文件是给连接器用的。
4>在连接器连接程序时,会依据用户程序中导入的头文件,将对应的库函数导入到程序中。头文件以.h为后缀名,库函数,当前系统里面实现,window .dll linux- .a .so
public native String getStringFromJni();
程序运行时,java虚拟机会在加载的本地.so运行库中查找与java本地方法匹配的c函数,并生成映射表,而后将java本地方法与c函数链接在一起,当java虚拟机在本地
的.so库查找不到与本地方法匹配的本地函数时,会抛出UnsatisfiedLinkError错误,那么java虚拟机在加载.so库的时候,如何把java代码的本地方法与运行库的c函数映射在一起呢?函数原型,当生成了函数原型,java虚拟机即可以把java本地方法和.so库的相应函数映射在一起,函数原型由javah工具生成,用来生成包含函数原型的c/c++头文件
JNIEXPORT jstring JNICALL Java_com_example_jnidem01_MainActivity_getStringFromJni
(JNIEnv *, jobject);
JNIEXPORT,JNICALL是JNI的关键字,表示此函数要被JNI调用,函数原型必须要有这两个关键字,JNI才能正确的调用函数,都是宏定义,函数原型遵循一定的规则形式为: Java_类名_本地方法名, 通过函数原型的命名即可以推断出JNI本地函数与哪个java类哪个本地方法对应。在生成的函数原型中,带有两个默认参数,JNIEnv ,jobject,支持JNI的函数必须包含这两个共同参数JNIEnv 是一个JNI接口指针 ,一个指向jni函数表的接口指针,用来调用JNI表中的各种JNI函数(jni.h头文件就是一个函数表) jobject,JNI提供的Java本地类型,用来在C代码中访问java对象,此参数中保存着调用地方法的对象的引用
六、Android.mk文件
Makefile
就是linux环境下进行c/c++开发的自动化编译的脚本,一套编译规则,c /cpp —-gcc–a.o -/.a。Android.mk文件用来告知NDK Build 系统关于Source的信。Android.mk将是GNU Makefile的一部分,且将被Build System解析一次或多次。Android.mk文件语法允许我们将Source打包成一个”modules”. modules可以是:静态库(.a), 动态库(.so)只有动态库可以被 install/copy到应用程序包(APK). 静态库则需要被链接入动态库。可以在一个Android.mk中定义一个或多个modules. 也可以将同一份source 加进多个modules.Build System帮我们处理了很多细节而不需要我们再关心。例如:你不需要在Android.mk中列出头文件和外部依赖文件。NDK Build System自动帮我们提供这些信息。这也意味着,当用户升级NDK后,你将可以受益于新的toolchain/platform而不必再去修改Android.mk.
Android.mk语法
LOCAL_PATH := $(call my-dir)
每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。
宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。
include $(CLEAR_VARS)
CLEAR_VARS 变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx.例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等,但不清理。
LOCAL_PATH.
这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能避免相互影响。
LOCAL_MODULE := hello-jni
LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System会自动添加适当的前缀和后缀。例如,foo,要产生动态库,则生成libfoo.so. 但请注意:如果模块名被定为:libfoo.则生成libfoo.so. 不再加前缀。
LOCAL_SRC_FILES := hello-jni.cpp
LOCAL_SRC_FILES变量必须包含将要打包如模块的C/C++ 源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++源码的扩展名为.cpp. 也可以修改,通过LOCAL_CPP_EXTENSION。
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息,编译打包为动态.so库
BUILD_STATIC_LIBRARY:编译为静态库 .a
BUILD_SHARED_LIBRARY :编译为动态库 .so
BUILD_EXECUTABLE:编译为Native C可执行程序
七、调用多个C文件
add.h add.cpp
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := itcast
LOCAL_SRC_FILES := itcast.cpp add.cpp 空格
include $(BUILD_SHARED_LIBRARY)
c++调用c函数(静态库方式)
1 声明头文件 2 实现函数 3 调用函数 4 修改Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ADD
LOCAL_SRC_FILES := add.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := itcast
LOCAL_SRC_FILES := itcast.cpp
LOCAL_STATIC_LIBRARIES := ADD
include $(BUILD_SHARED_LIBRARY)