JNI
JNI概念:
JNI全称Java Native Interface,Java本地化接口,可以通过JNI调用系统提供的API。操作系统,无论是Linux,Windows还是Mac OS,或者一些汇编语言写的底层硬件驱动都是C/C++写的。Java和C/C++不同,它不会直接编译成平台机器码,而是编译成虚拟机可运行的Java字节码的.class文件。通过JIT技术即时编译成本地机器码,所以有效率就比不上C/C++代码,JNI技术就解决了这一痛点。JNI可以说是C语言和Java语言交流的配适器,中间件。
JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一些。
JNI与NDK的区别:
JNI:是一套编程接口,用来实现Java代码与本地C/C++代码进行交互
NDK:NDK是Google开发的一套开发和编译工具集,可以生产动态链接库,主要用于Android的JNI开发。
JNI的作用:
- 扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
- 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
- 复用: 在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
- 特殊: 产品的核心技术一般也采用JNI开发,不易破解;
JNI在Android中作用:
JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik 虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;
- 应用层: 该层是由JNI开发,主要使用标准JNI编程模型;
- 应用框架层: 使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;
在Android中使用JNI
要使用它,就要先配置它:
首先在SDK Manager中下载NDK,CMake,LLDB
然后创建一个JNIUtil的类:
public class JNIUtil {
static {
System.loadLibrary("JNITestSample");
}
public static native String getWorld();
}
然后在Build菜单栏中,make Project一下
完成之后,会在app/build/intermediates下出现一个classes文件夹。
然后通过Terminal进入classes文件夹下的debug文件夹下,在终端运行:
cd app/build/intermediates/classes/debug/
javah com.example.asus1.learnjniandndk.JNIUtil
//记得找到自己对应的包
如果无法使用javah,提示说javah既不是内部命令也不是外部命令,就是java的环境变量没有配置,配置好java JDK的环境变量后,重启AS或者电脑,就可以用了
运行成功之后打开app/build/intermediates/classes/debug/
即可找到编译出的头文件"com.example.asus1.learnjniandndk.JNIUtil.h"
,不难发现头文件名是有原报名+类名组成。
创建jni开发的文件夹
- 点击app文件夹,New → Folder → JNI Folder, 选择在main文件夹下即可,生成成功后main目录下会出现一个jni的文件夹
- 找到刚才生成到头文件,复制到jni文件夹下(记得关闭刚才使用的终端,否则无法复制)
- 头文件有了,现在在jni目录下创建一个C++文件用于开发使用,命名与头文件相同,后缀为.cpp
- 编写C++文件中定义函数的代码
#include "com_example_asus1_learnjniandndk_JNIUtil.h"
JNIEXPORT jstring JNICALL Java_com_example_asus1_learnjniandndk_JNIUtil_getWorld
(JNIEnv *env, jclass) {
// new 一个字符串,返回Hello World
return env -> NewStringUTF("Hello World ZZZZZ");
}
然后在jni文件中创建Android.mk,和Application.mk这个文件:
//Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNITestSample
LOCAL_SRC_FILES := com_example_asus1_learnjniandndk_JNIUtil.cpp
include $(BUILD_SHARED_LIBRARY)
//application.mk
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions -std=c++0x
APP_ABI := armeabi
APP_ABI := armeabi-v7a
APP_PLATFORM := android-18
然后在终端cd到jni这个文件夹,执行ndk-build
执行完后,会生成,os文件
然后在build.gradle中加下面几句:
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.asus1.learnjniandndk"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
moduleName "JNITestSample"
abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种平台下的so库
ldLibs "log", "jnigraphics", "android" //jni中需要用到的其它库
stl "gnustl_shared"
}
sourceSets {
main {
jni.srcDirs = []
jniLibs.srcDirs 'src/main/libs'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
应该自己会在local.properties下自动添加ndk的路径:
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Jun 11 13:10:14 CST 2018
ndk.dir=D\:\\SDK\\ndk-bundle
sdk.dir=D\:\\SDK
然后在MainActivity中:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView view = findViewById(R.id.test);
view.setText(""+new JNIUtil().getWorld());
}
}
我们就会得到:
知识补充:
JNI类型映射:
引用数据类型:
需要注意的是,
1)引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
参考:
https://www.jianshu.com/p/ac00d59993aa
http://www.jcodecraeer.com/plus/view.php?aid=7769