Java - 写给 Android 应用开发者的 JNI 快速入门指北

Java Native Interface(Java本地接口) 简称 JNI,是一种编程框架,使得 JVM 中的 Java程序 可以调用本地应用/或库,也可以被其他程序调用。其过程可以不负责任的理解成 Java 的反射,因为代码逻辑和反射调用很像。

源码

本文所有涉及的所有源码:https://github.com/gavinliu/Study-JNI

平台编译环境
macOSgcc

HelloStringFromC

在java中编写native方法

public class HelloStringFromC {
    public static native String helloStringFromC();
    public static void main(String[] args) {
    }
 }

生成jni头文件

javac -jni HelloStringFromC

将会生成 HelloStringFromC.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class HelloStringFromC */
#ifndef _Included_HelloStringFromC
#define _Included_HelloStringFromC
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloStringFromC
 * Method:    helloStringFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC
  (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

编写native代码

#include <stdio.h>
#include <stdlib.h>
#include "HelloStringFromC.h"
JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC(JNIEnv* env, jclass cls) {
    return (*env)->NewStringUTF(env, "Hello");
 }

编译so库

gcc HelloStringFromC.c -fPIC -shared -o libhelloString.so

java调用

public class HelloStringFromC {
    public static native String helloStringFromC();
    public static void main(String[] args) {
        System.out.println(helloStringFromC());
    }
    static {
        System.load("/Users/gavin/Workspace/Study-JNI/01.HelloStringFromC/libhelloString.so");
    }
 }
> Hello

数据类型

基本数据类型

Java 数据类型JNI 数据类型C 数据类型
bleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble

引用类型

Java 数据类型JNI 数据类型
Objectjobject
Classjclass
Stringjstring
array[]jarray

数组类型

Java 数据类型JNI 数据类型
boolean[]jbooleanArray
byte[]jbyteArray
char[]jcharArray
short[]jshortArray
int[]jintArray
long[]jlongArray
float[]jfloatArray
doublejdoubleArray
objcetjobjectArray

JNIEnv

struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;

JNIEnv 其实是  JNINativeInterface_ 的指针别名,这个结构是C和Java交互的关键类,提供了C数据类型和JNI数据类型的互相转换和C调用Java的相关方法等。

Java 调 C

Java调C的套路:

  1. 在Java源码中编写native方法

  2. 使用 javah 生成 jni 头文件

  3. 实现 jni 方法

  4. 编译生成 so 动态链接库

  5. 在Java中使用动态链接库

静态调用

public static native String getFullName(String firstName);
// ==>
JNIEXPORT jstring JNICALL Java_CallC_getFullName
  (JNIEnv *, jclass, jstring);

静态调用生成的JNI方法,第二个参数为 jclass,代表Java中 CallC.class 这个对象。

对象调用

public native String getFirstName(String[] fullName);
// ==>
JNIEXPORT jstring JNICALL Java_CallC_getFirstName
  (JNIEnv *, jobject, jobjectArray);

对象调用生成的JNI方法,第二个参数为 jobject,代表Java中 CallC 这个对象。

示例

  1. Java 调 C 传入一个字符串,返回拼接后的新字符串

  2. Java 调 C 传入一个字符串数组,返回数组第一个元素

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CallC.h"
JNIEXPORT jstring JNICALL Java_CallC_getFullName
(JNIEnv* env, jclass jcl, jstring firstName) {
    // jstring -> char*
    const char* fn = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE);
    const char* ln = " Liu";
    char src[50], dest[50];
    strcpy(src,  ln);
    strcpy(dest, fn);
    // C的拼接字符串
    strcat(dest, src);
    return (*env)->NewStringUTF(env, dest);
 }
JNIEXPORT jstring JNICALL Java_CallC_getFirstName
(JNIEnv* env, jobject jobj, jobjectArray fullName) {
    // 从 对象数组中获取元素
    jstring elm = (jstring) (*env)->GetObjectArrayElement(env, fullName, 0);
    return elm;
 }
public class CallC {
    public static native String getFullName(String firstName);
    public native String getFirstName(String[] fullName);
    public static void main(String[] args) {
        System.out.println(getFullName("Gavin"));
        String[] fullName = {"Gavin", "Liu"};
        System.out.println(new CallC().getFirstName(fullName));
    }
    static {
        System.load("/Users/gavin/Workspace/Study-JNI/02.Java2C/libcallC.so");
    }
}
> Gavin Liu
                                                                > Gavin

C 调 Java

C调Java的套路:

  1. 获取 FieldID 或者 MethedID

  2. 通过 ID 可以 GET|SET Field,和调用 Methed

其中第一步需要用到 Field 和 Methed 签名,可以用 javap 命令获取:

javap -s -p CallJava
Compiled from "CallJava.java"
public class CallJava {
  public java.lang.String firstName;
    descriptor: Ljava/lang/String;
  public CallJava();
    descriptor: ()V
  public native java.lang.String changeName();
    descriptor: ()Ljava/lang/String;
  public native java.lang.String sayHiFromC();
    descriptor: ()Ljava/lang/String;
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
  public void sayHiFromJava();
    descriptor: ()V
  static {};
    descriptor: ()V
}

示例

  1. C 改变 Java 中的成员变量

  2. C 调用 Java 方法

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CallJava.h"
JNIEXPORT jstring JNICALL Java_CallJava_changeName
 (JNIEnv * env, jobject obj) {
     // 获取 jclass
     jclass cls = (*env)->GetObjectClass(env, obj);
     // 获取 fieldID
     jfieldID firstNameID = (*env)->GetFieldID(env, cls, "firstName", "Ljava/lang/String;");
     // 获取 属性
     jstring firstName = (*env)->GetObjectField(env, obj, firstNameID);
     // 拼接新字符串
     const char* str = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE);
     char src[50] = "super ";
     strcat(src, str);
     // 设置 属性
     jstring newName = (*env)->NewStringUTF(env, src);
     (*env)->SetObjectField(env, obj, firstNameID, newName);
     return newName;
 }
JNIEXPORT void JNICALL Java_CallJava_sayHiFromC
(JNIEnv * env, jobject obj) {
    // 获取 jclass
    jclass cls = (*env)->GetObjectClass(env, obj);
    // 获取 methodID
    jmethodID methodID = (*env)->GetMethodID(env, cls, "sayHiFromJava", "()V");
    // 调用方法
    (*env)->CallVoidMethod(env, obj, methodID);
    printf("%s\n", "sayHiFromC");
 }
public class CallJava {
    static {
        System.load("/Users/gavin/Workspace/Study-JNI/03.C2Java/libcallJava.so");
    }
    public native String changeName();
    public native void sayHiFromC();
    public String firstName = "Gavin";
    public static void main(String[] args) {
        CallJava c = new CallJava();
        System.out.println("c.firstName \t" + c.firstName);
        System.out.println("c.changeName() \t" + c.changeName());
        System.out.println("c.firstName \t" + c.firstName);
        c.sayHiFromC();
    }
    public void sayHiFromJava() {
        System.out.println("sayHiFromJava");
    }
}
c.firstName     Gavin
                                                                            c.changeName()     super Gavin
                                                                            c.firstName     super Gavin
                                                                            sayHiFromJava
                                                                            sayHiFromC

其他

JNIEnv 中还有很多方法,就不作一一介绍,掌握了其套路,代码就很容易写了,比如说调用方法是  CallVoidMethod,调用静态方法就是 CallStaticVoidMethod

C & C++ 的一些不同

JNIEnv 对象不一样

在C中: JNIEnv* 是一个二级指针,所以是使用  (*env )->  

在C++中: JNIEnv* 是一个一级指针,则是使用  env->

引用类型不一样

在C中:是使用结构体实现 

在C++中:是使用对象实现

官方文档

http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序邦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值