jni 极简示例

Posted 张保维

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jni 极简示例相关的知识,希望对你有一定的参考价值。

1

prepare wsl

apt update
apt install gcc
apt install cmake
apt install make
apt install gdb

public class HelloWorld 
    static 
        System.loadLibrary("hello");
    

    public static void main(String[] args) 
        HelloWorld helloWorld = new HelloWorld();
        String hello = helloWorld.displayHelloWorld("hello");
        System.out.println(hello);

        byte[] bytes = helloWorld.readFile("/mnt/c/Users/zhangbaowei/Desktop/test.txt");

        System.out.println(new String(bytes));

    

    public native String displayHelloWorld(String input);

    public native byte[] readFile(String input);

 javac -h . HelloWorld.java

CMakeLists.txt

set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(JAVA_AWT_LIBRARY "/root/.sdkman/candidates/java/current/lib")
set(JAVA_JVM_LIBRARY "/root/.sdkman/candidates/java/current/lib/server")
set(JAVA_INCLUDE_PATH "/root/.sdkman/candidates/java/current/include")
set(JAVA_INCLUDE_PATH2 "/root/.sdkman/candidates/java/current/include/linux")
set(JAVA_AWT_INCLUDE_PATH "/root/.sdkman/candidates/java/current/include")

find_package(JNI REQUIRED)
include_directories($JNI_INCLUDE_DIRS)

add_library(hello SHARED HelloWorld.c)

HelloWorld.h


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

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" 
#endif
/*
 * Class:     HelloWorld
 * Method:    displayHelloWorld
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_HelloWorld_displayHelloWorld
  (JNIEnv *, jobject, jstring);

/*
 * Class:     HelloWorld
 * Method:    readFile
 * Signature: (Ljava/lang/String;)[B
 */
JNIEXPORT jbyteArray JNICALL Java_HelloWorld_readFile
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus

#endif
#endif

HelloWorld.c

#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
#include <string.h>
#include <malloc.h>

JNIEXPORT jstring JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj, jstring input) 
    const char *input_str = (*env)->GetStringUTFChars(env, input, 0);
    printf("Received string: %s\\n", input_str);

    // Additional string to be appended
    const char *additional_str = ",abc";

    // Allocate memory for the output string
    char *output_str = (char *)malloc(strlen(input_str) + strlen(additional_str) + 1);

    // Concatenate input string and additional string
    strcpy(output_str, input_str);
    strcat(output_str, additional_str);

    // Release the input string memory
    (*env)->ReleaseStringUTFChars(env, input, input_str);

    // Create a new Java String from the output string
    jstring result = (*env)->NewStringUTF(env, output_str);

    // Free the memory allocated for the output string
    free(output_str);

    return result;


JNIEXPORT jbyteArray JNICALL Java_HelloWorld_readFile(JNIEnv *env, jobject obj, jstring input) 
    const char *input_file = (*env)->GetStringUTFChars(env, input, 0);
    FILE *file = fopen(input_file, "rb");

    if (file == NULL) 
        printf("Error: Unable to open file %s\\n", input_file);
        (*env)->ReleaseStringUTFChars(env, input, input_file);
        return NULL;
    

    // Get the file size
    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    // Allocate memory for the file content
    unsigned char *file_content = (unsigned char *)malloc(file_size);

    // Read the file content into memory
    size_t bytes_read = fread(file_content, 1, file_size, file);
    fclose(file);

    // Release the input string memory
    (*env)->ReleaseStringUTFChars(env, input, input_file);

    if (bytes_read != file_size) 
        printf("Error: Unable to read the entire file %s\\n", input_file);
        free(file_content);
        return NULL;
    

    // Create a new Java byte array and copy the file content into it
    jbyteArray result = (*env)->NewByteArray(env, file_size);
    (*env)->SetByteArrayRegion(env, result, 0, file_size, (jbyte *)file_content);

    // Free the memory allocated for the file content
    free(file_content);

    return result;

gcc -shared -fPIC -o libhello.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux HelloWorld.c

copy to java path


javac HelloWorld.java         # 重新编译
java -Djava.library.path=. HelloWorld

Android 使用 jni Demo示例

Android 使用 jni Demo示例

简介

1. NDK的介绍

1.1 NDK 简介

NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

Native Development Kit,是 Android的一个工具开发包
NDK是属于 Android 的,与Java并无直接关系

应用场景:在Android的场景下 使用JNI


即 Android开发的功能需要本地代码(C/C++)实现

1.2 NDK 特点


特点

额外注意

2. JNI介绍

2.1 JNI 简介

定义:Java Native Interface,即 Java 本地接口
作用: 使得 Java 与本地其他类型语言(如C、C++)交互

即在 Java 代码里调用 C、C++ 等语言的代码或  C、C++ 代码调用 Java 代码

特别注意:

  1. JNI 是 Java 调用 Native 语言的一种特性
  2. JNI 是属于 Java 的,与 Android 无直接关系

2.2 为什么要有 JNI?

背景:实际使用中,Java 需要与 本地代码 进行交互
问题:因为 Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱
解决方案: 采用 JNI特性 增强 Java 与 本地代码交互的能力

3. NDK 与 JNI 的关系

NDK下载及环境配置

1. 使用Android studio SDK Manager下载

点击Android studio 工具栏的 SDK Manager:

或者按照路径 File > Settings > Appearance & Behavior > System Settings > Android SDK或者Tool > SDK Manager打开SDK Manager 界面

选择SDK Tools 条目,勾选NDK和CMake后点击apply按钮等待下载完即刻

下载完成之后,ndk的位置在你的Android sdk目录下方,有一个ndk的文件夹。

2.配置NDK

2.1 配置环境变量

点击我的电脑右键,选择属性

关于里面找到高级系统设置 ,点击

点击环境变量

点击系统变量Path

添加ndk地址

在CMD窗口输入ndk-build 验证成功

2.2 Android studio配置NDK

下面是网上的流程,如果你可以用就直接用这个方法:

注意:实际上在我自己在我的Android studio中配置时,NDK Location是不可选择的状态,download也是如此,我来分享一下我在Android studio的配置过程

首先,我在 local.properties 中手动添加本地ndk.dir路径

但是,实际上这样仅仅是在刚刚的NDK Location显示了路径,查阅了官方文档,发现ndk.dir已弃用。
按照官方文档的说明,在我们从 SDK Manager 下载完成后,应该自动同步build文件

那么,你可以试着进入File — > Project Structure —> Modules

这样,我的问题就已经解决了,希望能给你带来参考!

示例Demo流程

1.版本介绍

Android Studio版本

2.Demo创建到运行流程

新建项目,创建成功后,

接下来开始写自定义的一个 native方法,新建一个 JniText.java文件,里面写一个 sayHello() 方法

点击Terminal cd 进入 java目录下 ,执行 javah -classpath . -jni com.android.test.jnidemo.JniText ,生成 com_android_test_jnidemo_JniText.h

// javah -classpath . -jni 包名.类名。
javah -classpath . -jni com.android.test.jnidemo.JniText

com_android_test_jnidemo_JniText.h 代码如下

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

#ifndef _Included_com_android_test_jnidemo_JniText
#define _Included_com_android_test_jnidemo_JniText
#ifdef __cplusplus
extern "C" 
#endif
/*
 * Class:     com_android_test_jnidemo_JniText
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_android_test_jnidemo_JniText_sayHello
  (JNIEnv *, jclass);

#ifdef __cplusplus

#endif
#endif

main新建JNI Folder,将com_android_test_jnidemo_JniText.h 移到jni目录xia


点击jni文件夹选择C++ Class 新建一个C++类,
名字命名为JNIHello(这里会自动创建一个.h和.cpp文件,只用.cpp文件即可)


复制com_android_test_jnidemo_JniText.h 中的方法到JNIHello.cpp,并引用#include "com_android_test_jnidemo_JniText.h"
修改后的JNIHello.cpp如下:
注意:com_android_test_jnidemo_JniText.h中的是接口方法中的参数是(JNIEnv *, jclass)
JNIHello.cpp中的是实现方法中的参数是(JNIEnv *env, jclass type)

//
// Created by on 2022/12/10.
//

#include "com_android_test_jnidemo_JniText.h"

JNIEXPORT jstring JNICALL Java_com_android_test_jnidemo_JniText_sayHello
   (JNIEnv *env, jclass type)
 return env->NewStringUTF("Hello World From JNI!!!!!");

在jni目录下,添加一个Android.mk文件,其目录结构如下:

Android.mk文件里面编写如下内容

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

# 定义生成的so库名称(Hello.so),以及编译该库包含的源文件,
LOCAL_MODULE  := Hello
LOCAL_SRC_FILES := JNIHello.cpp

# 定义生成动态so库
include $(BUILD_SHARED_LIBRARY)

关于Android.mk语言后面会单独写一篇文章进行讲解,这里重点说上面代码的内容

LOCAL_PATH := $(call my-dir):每个Android.mk文件必须以定义开始。它用于在开发tree中查找源文件。宏my-dir则由Build System 提供。返回包含Android.mk目录路径。
include $(CLEAR_VARS) :CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。这个清理是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响。
LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabd,则生成libabc.so。不再添加前缀。
LOCAL_SRC_FILES := ndkdemotest.c:这行代码表示将要打包的C/C++源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp。
include $(BUILD_SHARED_LIBRARY):BUILD_SHARED_LIBRARY是Build System提供的一个变量,指向一个GUN Makefile Script。它负责收集自从上次调用include $(CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型
BUILD_STATIC_LIBRARY:编译为静态库
BUILD_SHARED_LIBRARY:编译为动态库
BUILD_EXECUTABLE:编译为Native C 可执行程序
BUILD_PREBUILT:该模块已经预先编译

在jni目录下,在添加一个Android.mk文件,其目录结构如下:

Application.mk文件里面编写如下内容

 
APP_ABI := all

然后在build.gradle中添加如下代码:

 ndk 
            //如果要答应log就需要添加, 否者会报log函数未定义
            // ldLibs "log"
            指定生成模块名字,也就是最终的动态库名hello-jni,相应库文件名libhello-jni.so moduleName "hello-jni"
            moduleName "Hello"
            // 设置支持的SO库架构,根据实际系统配置so库存放路径
            abiFilters  'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        
externalNativeBuild 

        ndkBuild 
            path "src/main/jni/Android.mk"
        

    


JniText添加如下代码:

 static  
        System.loadLibrary("Hello");
    


MainActivity调用JniText中的sayHello,代码如下:

 String result=JniText.sayHello();
        // 打印JNI本地方法返回的字符串。
        Log.d(TAG, "the string from JNC C '"+result + "'");


点击运行:

成功!!!!!!

3.添加JNI LOG

Android.mk配置文件里面加上下面的代码

LOCAL_LDLIBS :=-llog

注意Android.mk里有一行include $(CLEAR_VARS) 必须把LOCAL_LDLIBS :=-llog放在它后面才有用, 否则相当于没写

然后在jni文件集里面写log_help.h文件,内容如下

#ifndef JNIUTILSAS_ANDROID_UTILS_H
#define JNIUTILSAS_ANDROID_UTILS_H

#include <android/log.h>

#define TAG "TAG_JNI"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG, __VA_ARGS__) // 定义LOGF类型



#endif //JNIUTILSAS_ANDROID_UTILS_H

使用,在.h或者.cpp文件中引用log_help.h,然后, LOGD("Hello World JNI LOG");


//
// Created by on 2022/12/10.
//
#include "android_utils.h"
#include "com_android_test_jnidemo_JniText.h"



extern "C" JNIEXPORT jstring JNICALL Java_com_android_test_jnidemo_JniText_sayHello
   (JNIEnv *env, jclass type)
    LOGD("Hello World JNI LOG");
 return env->NewStringUTF("Hello World From JNI!!!!!");

运行结果如下:

4.生成SO

点击Terminal cd进入jni目录下 ,执行 ndk-build ,如下:

生成SO文件在libs文件下:

5. Demo 地址

AndroidJNIDemo

到此结束。

以上是关于jni 极简示例的主要内容,如果未能解决你的问题,请参考以下文章

Android 使用 jni Demo示例

Android JNI开发示例

Android实时显示时间日期(极简)

FFmpeg 示例代码,用于从静止图像 JNI Android 创建视频文件

极简示例揭示 SwiftUI 中 @ObservedObject 与 @StateObject 状态的关键区别

极简示例揭示 SwiftUI 中 @ObservedObject 与 @StateObject 状态的关键区别