JNI和NDK基础

引言

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类型描述
jbooleanboolean无符号8位整型
jbytebyte有符号8位整型
jcharchar无符号16位整型
jshortshort有符号16位整型
jintint32位整型
jlonglong64位整型
jfloatfloat32位浮点型
jdoubledouble64位浮点型
voidvoid无类型

引用类型主要有类、对象和数组,如下表格

JNI类型Java类型描述
jobjectObjectObject类型
jclassClass
jstringString字符串
jobjectArrayObject[]Object数组
jbooleanArrayboolean[]boolean数组
jbyteArraybyte[]byte数组
jcharArraychar[]char数组
jshortArrayshort[]short数组
jintArrayint[]int数组
jlongArraylong[]long数组
jfloatArrayfloat[]float数组
jdoubleArraydouble[]double数组
jthrowableThrowableThrowable
  • JNI签名

类的签名,它采用“L+包名+类名+;”的形式,只需要将其中的.替换为/即可,例如:java.lang.String,它的签名为Ljava/lang/String;。
基本数据类型签名采用一系列大写字母来表示,如下:

Java类型签名Java类型签名
booleanZlongJ
byteBfloatF
charCdoubleD
shortSvoidV
intI

数组签名根据基本类型和类的签名在前面加上“[”,例如 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:\test projects\JniDemo2>cd jni/src/main/java/com/fomin/demo/jni
D:\test projects\JniDemo2\jni\src\main\java\com\fomin\demo\jni>javac JniDes.java
D:\test projects\JniDemo2\jni\src\main\java>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("加密开始\n");
    return env->NewStringUTF("encry success");
}


JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_decrypt
  (JNIEnv *env, jobject thiz, jstring value){
     // 解密逻辑
     printf("解密开始\n");
     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("加密开始\n");
    return (*env)->NewStringUTF(env,"encry success");
}


JNIEXPORT jstring JNICALL Java_com_fomin_demo_jni_JniDes_decrypt
  (JNIEnv *env, jobject thiz, jstring value){
     // 解密逻辑
     printf("解密开始\n");
     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可执行程序   
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\n", str);
}

private void callInstanceMethod(String str) {
    System.out.format("JniDes::callInstanceMethod called!-->str=%s\n", 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调用静态方法和非静态方法,非静态方法多了一步构造对象的过程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值