引言

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:\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可执行程序
  • 创建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\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调用静态方法和非静态方法,非静态方法多了一步构造对象的过程。

JNI和NDK基础的更多相关文章

  1. [ 转载 ] Android JNI(一)——NDK与JNI基础

    Android JNI(一)——NDK与JNI基础 隔壁老李头 关注  4.4 2018.05.09 17:15* 字数 5481 阅读 11468评论 8喜欢 140 本系列文章如下: Androi ...

  2. Android JNI(一)——NDK与JNI基础

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  3. Android JNI和NDK学习(04)--NDK调试方法(转)

    本文转自:http://www.cnblogs.com/skywang12345/archive/2013/05/23/3092812.html 本文主要介绍在ndk中添加log的方法.然后,我们就可 ...

  4. JNI和NDK编程

    Java JNI的本意是Java Native Interface(Java本地接口),它是为了方便Java调用C.C++等本地代码所封装的一层接口.通过Java JNI,用户可以调用C.C++所编写 ...

  5. JNI与NDK简介

    最近稍微了解一下JNI和NDK. 网上各种教程给人一种二者不分的感觉, 经过自己运行代码, 将两者的关系理了一下. 就目前了解,JNI应该是java自带的一种调用c和c++等语言(native cod ...

  6. JNI和NDK的区别

    http://blog.csdn.net/ithomer/article/details/6828830 NDK(Native Development Kit)“原生”也就是二进制 android常用 ...

  7. 【转】 Android 开发 之 JNI入门 - NDK从入门到精通

    原文网址:http://blog.csdn.net/shulianghan/article/details/18964835 NDK项目源码地址 : -- 第一个JNI示例程序下载 : GitHub  ...

  8. 【转】JNI和NDK的区别

    原文网址:http://blog.csdn.net/ithomer/article/details/6828830 NDK(Native Development Kit)“原生”也就是二进制 andr ...

  9. 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化

    第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...

随机推荐

  1. 《mysql必知必会》学习_第16章_20180807_欢

    第16章:创建高级联结. P106 select concat(RTrim(vend_name),'(',RTrim(vend_country),')') as vend_title from ven ...

  2. 循环结构-for,while,do-while

    for,while,do-while >完整的循环应该包含以下四个部分: 初始化语句:初始化工作,在循环体开始之前执行 条件表达式:一个boolean表达式,决定是否执行循环体 循环体:反复执行 ...

  3. [转]决策树在Kaldi中如何使用

    转自:http://blog.csdn.net/chenhoujiangsir/article/details/51613144 说明:本文是kaldi主页相关内容的翻译(http://kaldi-a ...

  4. 设计模式总结(Java)—— 单例模式

    1. 定义 为了确保一个类有且仅有一个实例,而且自行实例化并向整个系统提供这个实例. 2. 使用场景 确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有 ...

  5. 正确使用AES对称加密

    正确使用AES对称加密 经常我看到项目中有人使用了对称加密算法,用来加密客户或项目传输中的部分数据.但我注意到开发 人员由于不熟悉原理,或者简单复制网上的代码示例,有导致代码存在安全风险. 我经常遇到 ...

  6. php省市联动实现

    设计模式:ajax实现,数据库格式:id,name,parent_id 数据库: CREATE TABLE IF NOT EXISTS `city` ( `id` ) NOT NULL AUTO_IN ...

  7. H5拖动火狐自动打开新标签

    写在前面的话:<H5拖动火狐自动打开新标签>原因是为什么百度很多了我就不细说,本文章只说我自己的解决方法... 自定义数据里写个链接,如果火狐自动打开就跳到这个链接 这个页面就写一句话:关 ...

  8. shell编程中的循环语句

    while循环直接从文件中读取 while read line do command done < filename until循环 until 条件 do command done for循环 ...

  9. 关于微信JS SDK接口wx.previewImage预览接口的使用

    然后后之前的项目,突然往微信上迁移了,一些微信的接口没怎么用过,比较陌生,这次的功能是想调用微信的接口,实现图片放大的功能, 就找到官方文档:http://qydev.weixin.qq.com/wi ...

  10. Python --代码风格检查 pep8

    pip3 install pycodestyle pycodestyle .. Pep8编码规范 https://blog.csdn.net/ratsniper/article/details/789 ...