Android JNI编程—JNI基础

什么是JNI,怎么使用

JNI——Java Native Interface,它是Java平台的一个特性(并不是Android系统特有的)。其实主要是定义了一些JNI函数,让开发者可以通过调用这些函数实现Java代码调用C/C++的代码,C/C++的代码也可以调用Java的代码,这样就可以发挥各个语言的特点了。那么怎么使用JNI呢,一般情况下我们首先是将写好的C/C++代码编译成对应平台的动态库(windows一般是dll文件,linux一般是so文件等),这里我们是针对Android平台,所以只讨论so库。由于JNI编程支持C和C++编程,这里我们的栗子都是使用C++,对于C的版本可能会有些差异,但是主要的内容还是一致的,大家可以触类旁通。

举一个简单的例子

这里还是直接从代码说起,这样更加形象和直观,也便于理解。今天使用的Java代码如下:

public class AndroidJni {

    static{
        System.loadLibrary("main");
    }

    public native void dynamicLog();

    public native void staticLog();

}

这里我们定义了两个声明为native的方法,并声明了一块静态区域,在该静态区域类加载名为libmain.so的库,这里我们说是libmain.so库,但是加载的时候却只写了“main”,其实大家只要知道这是约定的就可以了。

C++代码如下:

#include <jni.h>

#define LOG_TAG "main.cpp"

#include "mylog.h"

static void nativeDynamicLog(JNIEnv *evn, jobject obj){

    LOGE("hell main");
}

JNIEXPORT void JNICALL Java_com_github_songnick_jni_AndroidJni_staticLog (JNIEnv *env, jobject obj)
{
    LOGE("static register log ");

}

JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog},};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {

    JNIEnv *env;
    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        return -1;
    }
    LOGE("JNI_OnLoad comming");
    jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");

    env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));

    return JNI_VERSION_1_4;
}

这里引用了两个头文件,jni.h和mylog.h,其中jni.h是定义了很多我们使用的JNI函数和结构体,mylog.h是我自己定义的打印Android log的函数(功能和Java的Log类相同)。

这里暂时不讨论怎么编译成so库以及so库的一些规范。这里假定将上面的C++程序编译成了一个叫libmain.so的文件。在Java层使用System.loadLibarary("main")方法将该so库加载起来,使得dynamicLog()、staticLog()和对应的Java_com_github_songnick_jni_AndroidJni_staticLog()、nativeDynamicLog()两个native方法链接起来,当然这部分工作都是由Java虚拟机完成的,那么具体是怎么完成的,接下来将根据上面的代码进行分析。

静态注册native方法

在上面的代码中看到了JNIEXPORT和JNICALL关键字,这两个关键字是两个宏定义,他主要的作用就是说明该函数为JNI函数,在Java虚拟机加载的时候会链接对应的native方法,在AndroidJni.java的类中声明了staticLog()为native方法,他对应的JNI函数就是Java_com_github_songnick_jni_AndroidJni_staticLog(),那么是怎么链接的呢,在Java虚拟机加载so库时,如果发现含有上面两个宏定义的函数时就会链接到对应Java层的native方法,那么怎么知道对应Java中的哪个类的哪个native方法呢,我们仔细观察JNI函数名的构成其实是:Java_PkgName_ClassName_NativeMethodName,以Java为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数了。一般情况下我们可以自己手动的去按照这个规则写,但是如果native方法特别多,那么还是有一定的工作量,并且在写的过程中不小心就有可能写错,其实Java给我们提供了javah的工具帮助生成相应的头文件。在生成的头文件中就是按照上面说的规则生成了对应的JNI函数,我们在开发的时候直接copy过去就可以了。这里上面的代码为例,在AndroidStudio中编译后,进入项目的目录app/build/intermediates/classes/debug下,运行如下命令:

javah -d jni com.github.songnick.jni.AndroidJni

这里-d指定生成.h文件存放的目录(如果没有就会自动创建),com.github.songnick.jni.AndroidJni表示指定目录下的class文件。这里简单介绍一下生成的JNI函数包含两个固定的参数变量,分别是JNIEnv和jobject,其中JNIEnv后面会介绍,jobject就是当前与之链接的native方法隶属的类对象(类似于Java中的this)。这两个变量都是Java虚拟机生成并在调用时传递进来的。

动态注册

上面我们介绍了静态注册native方法的过程,就是Java层声明的native方法和JNI函数是一一对应的,那么有没有方法让Java层的native方法和任意的JNI函数链接起来,当然是可以的,这就得使用动态注册的方法。接下来就看看如何实现动态注册的。

JNI_OnLoad函数

当我们使用System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个函数并调用该函数,因此可以在该函数中做一些初始化的动作,其实这个函数就是相当于Activity中的onCreate()方法。该函数前面有三个关键字,分别是JNIEXPORT、JNICALL和jint,其中JNIEXPORT和JNICALL是两个宏定义,用于指定该函数是JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,用于衔接Java层和JNI层,至于这些数据类型我们在后面介绍。这里的jint对应Java的int数据类型,该函数返回的int表示当前使用的JNI的版本,其实类似于Android系统的API版本一样,不同的JNI版本中定义的一些不同的JNI函数。该函数会有两个参数,其中*jvm为Java虚拟机实例,JavaVM结构体定义了以下函数:

DestroyJavaVM
AttachCurrentThread
DetachCurrentThread
GetEnv

这里我们使用了GetEnv函数获取JNIEnv变量,上面的JNI_OnLoad函数中有如下代码:

JNIEnv *env;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

    return -1;
}

这里调用了GetEnv函数获取JNIEnv结构体指针,其实JNIEnv结构体是指向一个函数表的,该函数表指向了对应的JNI函数,我们通过调用这些JNI函数实现JNI编程,在后面我们还会对其进行介绍。

获取Java对象,完成动态注册

上面介绍了如何获取JNIEnv结构体指针,得到这个结构体指针后我们就可以调用JNIEnv中的RegisterNatives函数完成动态注册native方法了。该方法如下:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

第一个参数是Java层对应包含native方法的对象(这里就是AndroidJni对象),通过调用JNIEnv对应的函数获取class对象(FindClass函数的参数为需要获取class对象的类描述符):

jclass clz = env->FindClass("com/test/jni/AndroidJni");

第二个参数是JNINativeMethod结构体指针,这里的JNINativeMethod结构体是描述Java层native方法的,它的定义如下:

typedef struct {
    const char* name;//Java层native方法的名字
    const char* signature;//Java层native方法的描述符
    void*       fnPtr;//对应JNI函数的指针
} JNINativeMethod;

第三个参数为注册native方法的数量。一般会动态注册多个native方法,首先会定义一个JNINativeMethod数组,然后将该数组指针作为RegisterNative函数的参数传入,所以这里定义了如下的JNINativeMethod数组:

JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog}};

最后调用RegisterNative函数完成动态注册:

env->RegisterNatives(clz, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]));

JNIEnv结构体

上面提到JNIEnv这个结构体,它就老厉害了,指向一个函数表,该函数表指向一系列的JNI函数,我们通过调用这些JNI函数可以实现与Java层的交互,这里简单的看看几个定义的函数:

..........
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jboolean GetBooleanField(jobject obj, jfieldID fieldID)
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
CallVoidMethod(jobject obj, jmethodID methodID, ...)
CallBooleanMethod(jobject obj, jmethodID methodID, ...)
..........

这里简单的看看上面的四个函数,GetFieldID()函数是获取Java对象中某个变量的ID,GetBooleanField()函数是根据变量的ID获取数据类型为Boolean的变量。GetMethodID()函数是获取Java对象中对应方法的ID,CallVoidMethod()根据methodID调用对应对象中的方法,并且该方法的返回值为Void类型。通过这些函数我们可以实现调用Java层的代码。

原文地址:https://www.cnblogs.com/cmai/p/8168111.html

时间: 2024-10-12 08:07:58

Android JNI编程—JNI基础的相关文章

【转】Android JNI编程—JNI基础

原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它们都用到了JNI编程,并且JNI编程也贯穿了Android系统,学会JNI编程对于我们学习研究Android源码.Android安全以及Android安全加固等都是有所帮助的.但是对于我们这些写Android应用的,大部分时间都是在使用Java编程,很少使用C/C++编程,对于JNI编程也了解的比较

(转)Android高性能编程(1)--基础篇

关于专题     本专题将深入研究Android的高性能编程方面,其中涉及到的内容会有Android内存优化,算法优化,Android的界面优化,Android指令级优化,以及Android应用内存占用分析,还有一些其他有关高性能编程的知识.    随着技术的发展,智能手机硬件配置越来越高,可是它和现在的 PC 相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于 PC 的桌面应用程序.以上理由,足以需要开发人员更加专心去实现和优化你的代码了.选择合适的算

【Android高级】NDK/JNI编程技术基础介绍

作为一个Andoird的Java程序猿,会受到Java语言的局限.由于作为一面门向对象的语言不能像C/C++那样轻易调用与硬件有关的操作.因此JNI就搭建了这样一个桥梁,使Java和C/C++语言之间能够互相调用. 作为一个Javaproject师对C/C++的语言不是非常熟悉,但仅仅需熟悉他们之间调用的原理和方法,关于C/C++的编程就交给C语言project师去吧. 在这篇文章中主要介绍NDK/JIN搭建和基本用法. 一. 环境的搭建 二. 主要的使用 步骤: (1)新建Androidpro

Android jni 编程入门

本文将介绍如何使用eclipse和ndk-build来编写一个基于Android4.4版本的包含有.so动态库的安卓程序. 前提是已经安装和配置好了诸如SDK,NDK等编译环境.下面开始编程! 1 程序逻辑 我们要编写的程序包含两部分:java部分--负责界面和调用JNI native函数:JNI native 部分--负责native函数的具体实现(本文使用C语言). native 函数伪代码如下: ? 1 2 3 4 5 6 7 8 /* funtion: 传入两个整形变量,计算他们之和 r

Android jni 编程3(对基本类型一维整型数组的操作)总结版

主要学习资料:黑马程序员的NDK方法使用(生产类库so)              jni编程指南中文版(已上传至博客园) 博主文章(它使用的是VS和eclipse联合开发):http://www.cnblogs.com/activity-life/p/3643047.html //0.传入一维整型数组,无返回值(但已对数组进行了修改) public native void arrayEncode(int[] arr); //1.传入一维整型数组,数组长度(因为c不容易获取而Java方便),返回

android windows 上JNI编程

昨天学习windows上的JNI编程,JNI说白了就是java和c语言的一个互相沟通的桥梁,java可以调用JNI来完成调用C语言实现的方法.JNI的全称是(Java native interface),其实在编程重你只需要将与java交互的函数写出来,其他的C语言内部调用的就可以直接使用C语言相关语法了.闲话少说,开始正题吧. 要想在windroid或者是linux上使用JNI必须要下载NDK的并且指定路径,在windows我们还需要安装一个sygwin,这里我就不再说怎样安装cygwin了,

【转】android JNI编程 一些技巧(整理)

原文网址:http://blog.csdn.net/linweig/article/details/5203716 本篇将介绍在JNI编程中如何传递参数和返回值. 首先要强调的是,native方法不但可以传递Java的基本类型做参数,还可以传递更复杂的类型,比如String,数组,甚至自定义的类.这一切都可以在jni.h中找到答案. 1. Java基本类型的传递 用过Java的人都知道,Java中的基本类型包括boolean,byte,char,short,int,long,float,doub

android学习之jni编程初探

使"java+c"的开发方式成为了官方支持的开发方式,NDK的本质就是为了让Android应用程序能方便的使用JNI技术而提供的一套工具集合,使用NDK主要是4大好处,第一个,代码的保护,,由于apk的java层代码很容易被反编译,而c/c++反汇编难度较大:第二个,可以很方便的使用开源库,因为很多的现存的开源库都是c/c++代码编写的:第三个,提高程序的执行效率,第四个,便于移植,使用c/c++写的库可以方便在其它的嵌入式平台上使用.这里还有一个问题就是如果在模拟器上运行的话,我们只

android开发教程(4)— jni编程之采用 javah 从java调用C++

用Java调用C/C++代码 当无法用 Java 语言编写整个应用程序时,JNI 允许您使用本机代码.在下列典型情况下,您可能决定使用本机代码: 希望用更低级.更快的编程语言去实现对时间有严格要求的代码. 希望从 Java 程序访问旧代码或代码库. 需要标准 Java 类库中不支持的依赖于平台的特性. 须知:SWIG和javah的区别(强烈推荐) 我看了网上的关于 jni编程 的教程很多,但不尽相同,刚开始会犯迷糊.我想笔者往往忽略了一个关键点,那就是采用了什么方式决定了步骤的流程.有两种生成