0%

JNI 和 NDK 编程(一):JNI 的开发流程

1. JNI 和 NDK 概述

  • JNI 即 Java Native Interface Java 本地接口,是为了方便 Java 调用 C、C++ 等本地代码所封装的一层接口
  • NDK 是 Android 所提供的一个工具集合,通过 NDK 可以在 Android 中更加方便地通过 JNI 来访问本地代码
  • NDK 还提供了交叉编译器,开发人员只需要简单地修改 mk 文件就可以生产特定 CPU 平台的 so 动态库
  • 使用 NDK 的好处:
    • 提高代码的安全性。由于 so 库反编译比较困难,因此 NDK 提高了 Android 程序的安全性
    • 可以很方便地使用目前已有的 C/C++ 开源库
    • 便于平台间的移植。通过 C/C++ 实现的动态库可以很方便地在其他平台上使用
    • 提高程序在某些特性情形下的执行效率,但是并不能明显提升 Android 程序的性能

2. JNI 开发流程概述

  1. 在 Java 中声明 native 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // Demo

    package com.szy;

    import java.lang.System;

    public class JniTest {

    static {
    System.loadLibrary("jni-test"); // 加载 so 库的规范,前缀 lib 和 后缀 .so 是不需要明确指出的
    }

    public static void main(String args[]) {
    JniTest jniTest = new JniTest();
    System.out.println(jniTest.get());
    jniTest.set("Hello World");
    }

    public native String get();
    public native void set(String str);
    }
  2. 编译 Java 源文件得到 class 文件,然后通过 javah 命令导出 JNI 的头文件

    1
    2
    javac com/szy/JniTest.java
    javah com.szy.JniTest
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 通过 javah 命令会在当前目录下产生一个 com_szy_JniTest.h 的头文件

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

    #ifndef _Included_com_szy_JniTest
    #define _Included_com_szy_JniTest
    #ifdef __cplusplus
    // 此宏定义的含义:指定 extern "C" 内部的函数采用 C 语言的命名风格来编译
    // 否则当 JNI 采用 C++ 来实现时,由于 C 和 C++ 编译过程中对函数的命名风格不同
    // 这将导致 JNI 在链接时无法根据函数名查找到具体的函数,那么 JNI 调用就无法完成
    extern "C" {
    #endif
    /*
    * Class: com_szy_JniTest
    * Method: get
    * Signature: () Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get
    (JNIEnv *, jobject);

    /*
    * Class: com_szy_JniTest
    * Method: set
    * Signature: (Ljava/lang/String;)V
    */
    JNIEXPORT void JNICALL Java_com_szy_JniTest_set
    (JNIEnv *, jobject, jstring);

    #ifdef __cplusplus
    }
    #endif
    #endif
    • 函数名的格式Java_包名_类名_方法名
    • JNIEnv*: 表示一个指向 JNI 环境的指针,可以通过它来访问 JNI 提供的接口方法
    • jobject: 表示 Java 对象中的 this
    • JNIEXPORT 和 JNICALL: 它们是 JNI 中所定义的宏,可以在 jni.h 这个头文件中查找到
  3. 实现 JNI 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // test.cpp

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

    JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get(JNIEnv *env, jobject thiz) {
    printf("invoke get in C++\n");
    return env->NewStringUTF("Hello from JNI");
    }

    JNIEXPORT void JNICALL Java_com_szy_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from C++\n");
    char* str = (char*) env -> GetStringUTFChars(string, NULL);
    printf("%s\n", str);
    env -> ReleaseStringUTFChars(string, str);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // test.c

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

    JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get(JNIEnv *env, jobject thiz) {
    printf("invoke get from C\n");
    return (*env) -> NewStringUTF(env, "Hello from JNI");
    }

    JNIEXPORT void JNICALL Java_com_szy_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from C\n");
    char* str = (char*) (*env) -> GetStringUTFChars(env, string, NULL);
    printf("%s\n", str);
    (*env) -> ReleaseStringUTFChars(env, string, str);
    }
  4. 编译 so 库并在 Java 中调用

    1
    2
    3
    4
    5
    6
    // 这里采用 gcc 编译 so 库
    C++: gcc -shared -I 本地的 jdk 安装路径/include -fPIC test.cpp -o libjni-test.so
    C: gcc -shared -I 本地的 jdk 安装路径/include -fPIC test.c -o libjni-test.so

    // 这里通过 Java 命令执行 Java 程序
    java -Djava.library.path=jni com.szy.JniTest
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 采用 C++ 产生 so 库,程序运行后产生的日志
    invoke get in C++
    Hello from JNI
    invoke set from C++
    Hello World

    // 采用 C 产生 so 库,程序运行后产生的日志
    invoke get from C
    Hello from JNI
    invoke set from C
    Hello World
-------------------- 本文结束感谢您的阅读 --------------------