一、JNI 的概念
1、概念
JNI全称 Java Native Interface,Java 本地接口。可以通过JNI调用系统提供的API。
2、JNI 与 NDK 的区别
- JNI:JNI是一套编程接口,用来实现Java代码与本地C/C++代码的交互。
- NDK:NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发。
3、JNI 的作用
- 扩展: JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由。
- 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存。
- 复用:在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子。
- 特殊: 产品的核心技术一般也采用JNI开发,不易破解。
二、JNI 两种注册方式
(一)、静态注册
开发流程如下:
-
在 Java 中先声明 native 方法。
-
编译 Java 源文件得到 .class 文件。
-
使用 Javah -jni 命令生成对应的头文件。
- 命令 javah -jni packagename.classname 生成由包名加类名命名的 jni 层头文件。
- 命令 javah -o custom.h packagename.classname,其中 custom.h 为自定义的文件名。
-
实现 JNI 里面的函数(生成.c文件),再在Java中通过System.loadLibrary加载 so 库。
PS:javah 是 JDK 自带的一个命令,-jni 参数表示将 class 中用到 native 声明的函数生成 JNI 规则的函数
Demo:
- 创建项目
首先创建一个 Android 项目,包名:com.zf.ndkdemo
- 创建引用本地库的工具类
创建一个 Java 类 NDKTools
package com.zf.ndkdemo;
public class NDKTools {
public static native String stringFromJNI();
}
- 在MainActivity中调用 NDKTools 的 stringFromJNI()方法
package com.zf.ndkdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.File;
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.sample_text);
tv.setText(NDKTools.stringFromJNI());
}
}
- 获取classes文件
(1)点击 AndroidStudio 中的 Make Project 或者 Rebuild Project 进行编译获取class文件。(Build --> Make Project 或者 Build --> Rebuild Project)
(2)编译完成之后,按照如下目录寻找class文件。
app/build/intermediates/classes/debug/com/zf/ndkdemo
- 获取.h文件
点击AndroidStudio下面Terminal,然后进入app/build/intermediates/classes/debug目录下,然后执行命令:javah -jni com.zf.ndkdemo.NDKTools 。如果一切顺利则会在app/build/intermediates/classes/debug下面生成com_zf_ndkdemo_NDKTools.h文件。如下图:
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_zf_ndkdemo_NDKTools */
#ifndef _Included_com_zf_ndkdemo_NDKTools
#define _Included_com_zf_ndkdemo_NDKTools
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_zf_ndkdemo_NDKTools
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zf_ndkdemo_NDKTools_stringFromJNI
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
- 增加对应的.c文件
在工程main目录下创建jni文件夹,然后将刚刚生成的.h剪切过来。然后再在jni目录下生成一个.c文件ndkdemo.c
//
// Created by zf on 2019/1/17.
//
#include "com_zf_ndkdemo_NDKTools.h"
JNIEXPORT jstring JNICALL
Java_com_zf_ndkdemo_NDKTools_stringFromJNI
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello world,我的第一行NDK代码");
}
- 添加并编写Android.mk文件
在jni目录下生成一个Android.mk文件,内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ndkdemo-jni
LOCAL_SRC_FILES :=ndkdemo.c
include $(BUILD_SHARED_LIBRARY)
- 修改相关配置文件
- local.properties 文件中添加 NDK 路径
ndk.dir=D\:\\Sdk\\ndk-bundle
sdk.dir=D\:\\Sdk
- 修改 app module 目录下的 build.gradle 文件,内容如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.zf.ndkdemo"
minSdkVersion 18
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk{
moduleName "ndkdemo-jni"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
sourceSets.main {
jni.srcDirs = []
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
- gradle.properties 文件中添加: Android.useDeprecatedNdk=true
- 修改引用类 NDKTools
public class NDKTools {
static {
System.loadLibrary("ndkdemotest-jni");
}
public static native String stringFromJNI();
}
over
(二)、动态注册
开发流程如下:
- 编写 Java 的 native 方法;
- 编写 JNI 函数的实现(函数名随意);
- 利用结构体 JNINativeMethod 保存 Java native 方法和 JNI 函数的对应关系;
- 利用 RegisterNatives(JNIEnv*, jclass, const JNINativeMethod*,jint) 注册类的所有本地方法;
- 第3、4步的代码在 JNI_OnLoad 方法中执行。
- 在编写 Java native 方法的类中通过 System.loadLibrary 加载 JNI 动态库,然后会自动调用 JNI_OnLoad 函数,完成动态注册。
三、JNI 数据类型映射
- 基本数据类型:
Java类型 | 本地类型(Native Type) | 描述 |
---|---|---|
boolean | jboolean | C/C++无符号8位整型 (unsigned char) |
byte | jbyte | C/C++带符号8位整型 (char) |
char | jchar | C/C+无符号16位整型 (unsigned short) |
short | jshort | C/C++带符号16位整型 (short) |
int | jint | C/C++带符号32位整型 (int) |
long | jlong | C/C++带符号64位整型 (long) |
float | jfloat | C/C++32位浮点型 (float) |
double | jdouble | C/C++64位浮点型 (double) |
基本数据类型的映射即在Java的基本数据类型前面添加 j 就是本地类型的基本数据类型
- 引用数据类型:
Java类型 | 本地类型(Native Type) | 描述 |
---|---|---|
Object | jobject | 任何java对象 |
Class | jclass | Class类对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcahrArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整形数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
void | void |
注意:
a、应用数据类型不能在Native层使用,需要根据 JNI 函数转化后才能使用;
b、多维数组都是引用类型。
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray = env->NewObjectArray(length ,intArrayClass , NULL);
- 方法和变量的id
当Native要调用Java层方法的时候,需要通过JNI函数获取对应的id,根据id调用 JNI 函数获取该方法。变量同理。
id 结构如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
时间问题,未完待续…