Android :JNI基本原理

    在Android Framework中,可以通过JNI有机的将Java上层与C/C++底层结合起来,JNI提供了一系列的接口,允许Java类与使用C/C++等其他编程语言编写的应用程序、模块、库进行交互操作。

在Java代码中通过JNI调用C函数的步骤如下:
1. 编写Java代码
2. 编译Java代码
3. 生成C语言头文件
4. 编写C代码
5. 生成C共享库
6. 运行Java程序
第一步:编写Java代码
HelloJNI.java

class HelloJNI
{
    //本地方法说明
    native void printHello();
    native void printString(String str);

    //加载库
    static { 
        System.loadLibrary("hellojni");
        }

    public static void main(String args[])
    {
        HelloJNI myJNI = new HelloJNI();
        //调用本地方法(实际用的是使用C语言编写的JNI本地函数)
        myJNI.printHello();
        myJNI.printString("Hello World from printString fun");
    }
}

1 本地方法说明:native关键字,声明本地方法,该方法与用C/C++编写的JNI本地函数相对应,这个关键字告知Java编译器,在Java代码中带该关键字的方法只是声明,具体实现由其他语言实现。去掉这个关键字之后编译代码时,Java编译器就会报错,抛出编译错误,告知该方法没有实现。
2 加载库:在Java类中仅声明了两个本地方法。然后调用System.loadLibrary(“hellojni”);方法,加载具体实现本地方法的C运行库,在不同的操作系统下加载的C运行库不同,就此例来说,在Windows操作系统下加载的是hellojni.dll,在linux操作系统下libhellojni.so
3 创建一个HelloJNI对象,调用对象的本地方法,实现对JNI本地函数的调用

第二步 编译Java 代码
javac HelloJNI.java
会在本地生成HelloJNI.class文件,此时如果直接执行编译好的字节码文件会抛出错误,没有加载到对应的C运行库这里写图片描述

第三步 生成C语言头文件
javah HelloJNI
后面跟类名称
会在本地生成HelloJNI.h。里面包含HelloJNI类中声明的本地方法相对应的C函数原型
HelloJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

这是javah命令生成的两个C函数原型,函数原型在Java类中声明的本地方法的基础上生成。查看个函数原型的注释,注释中表明了三个元素:类名,本地方法名,本地方法签名。只要生成了这样的函数原型,Java虚拟机就能把本地库函数与Java本地方法正常的链接在一起。

分析一下函数原型,
JNIEXPORT 与 JNICALL都是JNI的关键字,表示此函数要被JNI调用,函数原型中必须要有这两个关键字,JNI才能正常的调用函数,这两个是宏,被定义在JDK目录include/linux文件夹下jni_md.h中,
观察其函数原型名称,可以发现其命名遵循着一定的命名规则,JNI支持的命名规则是“Java_类名_本地方法名”。

分析一下参数
在生成的函数原型中,有两个默认参数,支持JNI的函数必须包含着两个共同参数。第一个参数JNIEnv * 是JNI接口的指针,用来调用JNI表中的各种JNI函数,这里的JNI函数(非JNI本地函数)是指JNI提供的基本函数集,用来在JNI本地函数中创建Java对象或调用相应方法
第二个参数默认参数类型是jobject也是JNI提供的Java本地类型用来在C代码中访问Java对象,此参数中保存着调用本地方法的对象的一个引用。
例如在上述代码中,myJNI对象调用了本地方法,所以上面两个JNI本地函数的第二个参数jobject中保存着对myJNI对象的引用
第三个jstring参数对应String str的参数

第四步
编写C/C++代码
hellojni.c

    #include "HelloJNI.h"
    #include <stdio.h>

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
{
        printf("Hello World!\n");
        return;
}


JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj,jstring string)
{
    const char *str=(*env)->GetStringUTFChars(env,string,JNI_TRUE);
    printf("%s!\n",str);
    return;
}

GetStringUTFChars是JNI函数,用来将Java字符串转换成C语言字符串。使用此函数转换字符串之后,再使用printf语句输出,即可将本地方法传递过来的字符串输出

形式
const jbyte * GetStringUTFChars(JNIEnv * env,jstring string,jboollean * isCopy)
说明
将Java字符串对象转换成UTF-8字符串(C字符串),并返回指针
参数
env –JNI接口指针
string–Java字符串对象
isCopy–当从JNI函数中返回得到字符串的时候,如果要的是原始字符串的一份拷贝就设置JNI_TRUE,如果是和原始字符串指向用一份数据,就被赋值为JNI_FALSE,此时本地函数绝不能修改字符串的内容,否则JVM中的原始字符串也会被修改,这会打破Java语言中字符串不可变的规则

第五步
生成C共享库
gcc -shared hellojni.c -o libhellojni.so
有的会提示找不到jni.h或者jni_md.h
执行
gcc -shared -I JDK_HOME/include -I JDK_HOME/include/linux hellojni.c -o libhellojni.so
其中JDK_HOME是自己安装JDK的路径,-I指定这两个文件的位置,也可以直接把这两个文件复制出来放到PATH环境变量中的其中任意一个路径下

第六步 运行Java程序
Java HelloJNI
有时候会出现链接不到库
这里写图片描述
设置环境变量
export LD_LIBRARY_PATH=’pwd’:$LD_LIBRARY_PATH
将本地路径加入到搜索动态库目录中
然后执行
Java HelloJNI
这里写图片描述

在梳理一下
(1)在Java类中声明本地方法
(2)使用javah命令,生成包含JNI本地函数原型的头文件
(3)实现JNI本地函数
(4)生成C共享库
(5)运行,通过JNI,调用JNI本地函数

本人新人,有错请指正!谢谢

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值