引言
JNI是Java Native Interface(Java本地接口),是为了方便Java调用C和C++等本地代码所封装的一层接口。
NDK是Android提供的一个工具集合,通过NDK可以在Android中更加方便地沟通JNI来访问本地代码。
两者之间的关系:JNI是实现目的,NDK是在Android实现JNI的手段。
使用NDK有如下好处:
- 提供代码的安全性
- 可以很方便的地使用目前已有的C/C++开源库
- 便于平台间的移植
- 提高程序在某些特定情形下的执行效率,但不能明显提升Android程序的性能
JNI基础
- JNI类型(基本类型和引用类型)
基本类型如下表格:
JNI类型 | Java类型 | 描述 |
---|---|---|
jboolean | boolean | 无符号8位整型 |
jbyte | byte | 有符号8位整型 |
jchar | char | 无符号16位整型 |
jshort | short | 有符号16位整型 |
jint | int | 32位整型 |
jlong | long | 64位整型 |
jfloat | float | 32位浮点型 |
jdouble | double | 64位浮点型 |
void | void | 无类型 |
引用类型主要有类、对象和数组,如下表格
JNI类型 | Java类型 | 描述 |
---|---|---|
jobject | Object | Object类型 |
jclass | Class | 类 |
jstring | String | 字符串 |
jobjectArray | Object[] | Object数组 |
jbooleanArray | boolean[] | boolean数组 |
jbyteArray | byte[] | byte数组 |
jcharArray | char[] | char数组 |
jshortArray | short[] | short数组 |
jintArray | int[] | int数组 |
jlongArray | long[] | long数组 |
jfloatArray | float[] | float数组 |
jdoubleArray | double[] | double数组 |
jthrowable | Throwable | Throwable |
- JNI签名
类的签名,它采用“L+包名+类名+;”的形式,只需要将其中的.替换为/即可,例如:java.lang.String,它的签名为Ljava/lang/String;。
基本数据类型签名采用一系列大写字母来表示,如下:
Java类型 | 签名 | Java类型 | 签名 |
---|---|---|---|
boolean | Z | long | J |
byte | B | float | F |
char | C | double | D |
short | S | void | V |
int | I |
数组签名根据基本类型和类的签名在前面加上“[”,例如 char[]数组,前面[C。对于多维数组,签名是n个[+类型签名,例如int[][],签名为[[I。
方法的签名为参数类型签名+返回类型签名,例如int fun(int i),签名为(I)I。
JNI开发流程
- 声明native方法
public class JniDes {
static {
System.loadLibrary("jni-des");
}
public native String encry(String value);
public native String decrypt(String value);
}
- Terminal命令生成JNI头文件
D: est projectsJniDemo2>cd jni/src/main/java/com/fomin/demo/jni
D: est projectsJniDemo2jnisrcmainjavacomfomindemojni>javac JniDes.java
D: est projectsJniDemo2jnisrcmainjava>javah com.fomin.demo.jni.JniDes
javac命令生成class文件,根据这个文件使用javah生成JNI头文件,以下是生成文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_fomin_demo_jni_JniDes */
#ifndef _Included_com_fomin_demo_jni_JniDes
#define _Included_com_fomin_demo_jni_JniDes
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_fomin_demo_jni_JniDes
* Method: encry
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_encry
(JNIEnv *, jobject, jstring);
/*
* Class: com_fomin_demo_jni_JniDes
* Method: decrypt
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_decrypt
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
头文件解析:
-
Java_com_fomin_demo_jni_JniDes_encry:表示是一个函数名,命名规则Java_包名_native的方法名
-
JNIEnv *:一个指向JNI环境的指针
-
jobject:Java对象中的this
-
jstring:参数名称
-
JNIEXPORT 和JNICALL :JNI定义的宏
-
实现JNI方法
- c++实现方式
#include "JniDes.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_encry
(JNIEnv *env, jobject thiz, jstring value){
// 加密逻辑
printf("加密开始
");
return env->NewStringUTF("encry success");
}
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_decrypt
(JNIEnv *env, jobject thiz, jstring value){
// 解密逻辑
printf("解密开始
");
return env->NewStringUTF("decrypt success");
}
* c实现方式
#include "JniDes.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_encry
(JNIEnv *env, jobject thiz, jstring value){
// 加密逻辑
printf("加密开始
");
return (*env)->NewStringUTF(env,"encry success");
}
JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_decrypt
(JNIEnv *env, jobject thiz, jstring value){
// 解密逻辑
printf("解密开始
");
return (*env)->NewStringUTF(env,"decrypt success");
}
c++和c实现方式类似,不同的是对env的操作,例如c++:env->,而c:(*env)->
NDK开发流程
- 配置NDK
- 创建Android.mk文件
LOCAL_PATH := $(call my-dir) #返回包含Android.mk的目录路径
include $(CLEAR_VARS) #变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx
LOCAL_MODULE := jni-des #模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格
LOCAL_SRC_FILES := JniTest.cpp #变量必须包含将要打包如模块的C/C++ 源码
include $(BUILD_SHARED_LIBRARY) #负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息。并决定编译为什么
# BUILD_STATIC_LIBRARY:编译为静态库。
# BUILD_SHARED_LIBRARY :编译为动态库
# BUILD_EXECUTABLE:编译为Native C可执行程序
- 创建Application.mk
APP_ABI := armeabi armeabi-v7a x86 #默认情况下,ndk构建系统为armeabi,ABI生成二进制文件
APP_PLATFORM = android-9 #使用的ndk库函数版本号。一般和SDK的版本相对应,各个版本在NDK目录下的platforms文件夹中
- 配置gradle文件
- 配置so库存放目录
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs']
jni.srcDirs = []
}
}
- 配置ndk build task
def getNdkDir() { // 获取系统的ndk目录
if (System.env.ANDROID_NDK_HOME != null)
return System.env.ANDROID_NDK_HOME
else
throw new GradleException("NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.")
}
def getNdkBuildCmd() { // 组合ndk文件
def ndkbuild = getNdkDir() + "\ndk-build"
if (Os.isFamily(Os.FAMILY_WINDOWS))
ndkbuild += ".cmd"
return ndkbuild
}
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
commandLine getNdkBuildCmd(),//配置ndk的路径
'NDK_PROJECT_PATH=build/intermediates/ndk',//ndk默认的生成so的文件
'NDK_LIBS_OUT=src/main/jniLibs',//配置的我们想要生成的so文件所在的位置
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',//指定项目以这个mk的方式
'NDK_APPLOCATION_MK=src/main/jni/Application.mk'//指定项目以这个mk的方式
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild //使用ndkBuild
}
- 生成so库
- 使用NDK方法
JniDes jniDes = new JniDes();
Log.d("MainActivity", jniDes.decrypt("test"));
Log.d("MainActivity", jniDes.encry("test"));
JNI调用Java方法
首先在JniDes类定义两个方法
private static void callStaticMethod(String str) {
System.out.format("JniDes::callStaticMethod called!-->str=%s
", str);
}
private void callInstanceMethod(String str) {
System.out.format("JniDes::callInstanceMethod called!-->str=%s
", str);
}
然后在JNI调用定义的静态方法
void callJavaStaticMethod(JNIEnv *env, jobject thiz){
jclass clazz=env->FindClass("com/fomin/demo/jni/JniDes")
if(clazz == null){
return;
}
jmethodID static_method= env->GetStaticMethodID(clazz,"callStaticMethod","(Ljava/lang/String;)V");
if (static_method == NULL) {
printf("找不到callStaticMethod静态方法");
return;
}
jstring str = env->NewStringUTF("我是静态方法");
env->CallStaticVoidMethod(clazz,static_method, str);
// 删除局部引用
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(str);
}
或者JNI调用定义的非静态方法
void callJavaMethod(JNIEnv *env, jobject thiz){
jclass clazz = env->FindClass("com/fomin/demo/jni/JniDes");
if (clazz == NULL) {
printf("找不到JniDes这个类");
return;
}
// 获取类的默认构造方法ID
jmethodID mid_construct = env->GetMethodID(clazz, "<init>","()V");
if (mid_construct == NULL) {
printf("找不到默认的构造方法");
return;
}
// 查找实例方法的ID
jmethodID mid_instance = env->GetMethodID(clazz, "callInstanceMethod", "(Ljava/lang/String;)V");
if (mid_instance == NULL) {
return;
}
// 创建该类的实例
jobject jobj = env->NewObject(clazz,mid_construct);
if (jobj == NULL) {
printf("找不到callInstanceMethod方法");
return;
}
// 调用对象的实例方法
jstring str = env->NewStringUTF("我是实例方法");
env->CallVoidMethod(jobj,mid_instance,str);
// 删除局部引用
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(jobj);
env->DeleteLocalRef(str);
}
JNI调用静态方法和非静态方法,非静态方法多了一步构造对象的过程。