Android JNI开发从0到1,java调C,C调Java,保姆级教程详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。
点击跳转到教程

第一步首先配置Android studio的NDK开发环境,首先在Android studio中下载NDK包

Android JNI开发从0到1,java调C,C调Java,保姆级教程详解_第1张图片
第二步在local.properties文件中,配置对应的NDK路径
Android JNI开发从0到1,java调C,C调Java,保姆级教程详解_第2张图片
第三歩,在app目录下的build.gradle文件中的,android{}闭包中,指定CMakeLists.txt路径

// 在android节点下
    // 指定CMakeLists.txt路径
    externalNativeBuild {
        cmake {
            // 在该文件种设置所要编写的c源码位置,以及编译后so文件的名字
            path 'CMakeLists.txt'
        }
    }

在defaultConfig闭包下配置

// 增加cmake控制属性
        externalNativeBuild {
            cmake {
                // 指定编译架构
                abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
            }
        }

在app目录下,指定CmakeLists.txt文件,根据注释进行相应添加

# CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
#CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
add_library(
      # 设置so文件名称.
       Hello
       # 设置这个so文件为共享.
       SHARED
       # Provides a relative path to your source file(s).
       src/main/jni/Hello.c)
add_library(
        # 设置so文件名称.
        Test
        # 设置这个so文件为共享.
        SHARED
        # Provides a relative path to your source file(s).
        src/main/jni/Test.c)
add_library(
        # 设置so文件名称.
        CCallJava
        # 设置这个so文件为共享.
        SHARED
        # Provides a relative path to your source file(s).
        src/main/jni/CCallJava.c)

# 添加 log 库的链接
target_link_libraries(CCallJava log)
target_link_libraries(Test log)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
       log-lib
       # Specifies the name of the NDK library that
       # you want CMake to locate.
       log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
            # 制定目标库.
            Hello
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib} )

第四步,首先进行Java代码C代码的操作
1.JNI2.java代码如下

/**
 * @Author: ly
 * @Date: 2023/8/6
 * @Description: java调C代码
 */
public class JNI2 {
    static {
        System.loadLibrary("Test"); //加载动态链接库
    }

    /**
     * 让C代码做加法运算,把结果返回
     * 场景:大量运算,编解码之类的,需要性能很高的情况下,可以用C代码
     *
     * @param x
     * @param y
     * @return
     */
    public native int add(int x, int y);

    /**
     * 从java传入字符串,C代码进行拼接
     *
     * @param s I am from java
     * @return I am from java and I am from c
     */
    public native String sayHello(String s);

    /**
     * 让C代码给每个元素都加上10
     *
     * @param intArray
     * @return
     */
    public native int[] increaseArrayEles(int[] intArray);

    /**
     * 应用:检查密码是否正确,如果正确返回200,否则返回400
     *
     * @param pwd
     * @return
     */
    public native int checkPwd(String pwd);
}

2.根据命令行javah +JNI2的全类名,生成Test.c对应的C代码头文件com_example_jniproject_JNI2.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_example_jniproject_JNI2 */

#ifndef _Included_com_example_jniproject_JNI2
#define _Included_com_example_jniproject_JNI2
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jniproject_JNI2
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_jniproject_JNI2_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_example_jniproject_JNI2
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jniproject_JNI2_sayHello
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_example_jniproject_JNI2
 * Method:    increaseArrayEles
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_example_jniproject_JNI2_increaseArrayEles
  (JNIEnv *, jobject, jintArray);

/*
 * Class:     com_example_jniproject_JNI2
 * Method:    checkPwd
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_example_jniproject_JNI2_checkPwd
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

3.根据jni协议,去写对应的c代码,创建Test.c文件,代码如下

//
// Created by DELL on 2023/8/6.
//
#include "com_example_jniproject_JNI2.h"
#include "string.h"
#include 

#include 

#define TAG "luyu"
#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类型


JNIEXPORT char *JNICALL Java_com_example_MyClass_myMethod(JNIEnv *env, jobject obj, jstring jstr) {
    const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);

    // 使用 cstr 进行操作,并获取结果(假设结果为 result)

    (*env)->ReleaseStringUTFChars(env, jstr, cstr);

    char *result = (char *) malloc(strlen(cstr) + 1);
    strcpy(result, cstr);

    return result;
}

/**
 * jint:返回值
 * Java_全类名_方法名
 * JNIEnv *env
 */
jint Java_com_example_jniproject_JNI2_add
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
    int result = ji + jj;
    return result;
};

/**

     * 从java传入字符串,C代码进行拼接
     *
     * @param s I am from java
     * @return I am from java and I am from c
*/

jstring Java_com_example_jniproject_JNI2_sayHello
        (JNIEnv *env, jobject jobj, jstring jstring1) {
    char *fromJava = Java_com_example_MyClass_myMethod(env, jobj, jstring1);
    char *fromC = " and I am from C";
    //拼接函数,拼接后得到的结果放到第一个参数里面
    strcat(fromJava, fromC);//把拼接的结果放在第一个参数里面
    //jstring     (*NewStringUTF)(JNIEnv*, const char*);
    LOGE("fromJava==%s\n", fromJava);
    return (*env)->NewStringUTF(env, fromJava);
};

/**
 * 给每个元素加10
 * 场景:图片处理,颜色矩阵(就是数组),进行数组的处理
 */
jintArray Java_com_example_jniproject_JNI2_increaseArrayEles
        (JNIEnv *env, jobject jobj, jintArray jintArray1) {
    //1.得到数组的长度
    //jsize       (*GetArrayLength)(JNIEnv*, jarray);
    jsize size = (*env)->GetArrayLength(env, jintArray1);
    //2.得到数组的元素
    // jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jint *intArray = (*env)->GetIntArrayElements(env, jintArray1,
                                                 JNI_FALSE);//这里传0 false表示 在同一份内存操作,不开辟新的内存
    //3.遍历数组,给每个元素加10
    int i;
    for (i = 0; i < size; i++) {
        *(intArray + i) += 10;
    }
    // 4. 同步修改到 Java 层
    (*env)->ReleaseIntArrayElements(env, jintArray1, intArray, 0);
    //4.返回结果
    return jintArray1;
};

/**
 * 应用:检查密码是否正确,如果正确返回200,否则返回400
 */
jint Java_com_example_jniproject_JNI2_checkPwd
        (JNIEnv *env, jobject jobj, jstring jstring1) {
    //假设服务器的密码是123456
    char *origin = "123456";
    char *fromUser = Java_com_example_MyClass_myMethod(env, jobj, jstring1);
    //函数比较字符串是否相同
    int code = strcmp(origin, fromUser);
    LOGE("code==%d\n", code);
    if (code == 0) {
        return 200;
    } else {
        return 400;
    }
};

第五步,C代码调用Java代码,首先创建JNI3.java

/**
 * @Author: ly
 * @Date: 2023/8/6
 * @Description: C代码调用Java代码
 */
public class JNI3 {
    static {
        System.loadLibrary("CCallJava"); //加载动态链接库
    }
    //当执行这个方法的时候,让C代码调用
    //public int add(int x, int y)
    public native void callbackAdd();

    /**
     * 当执行这个方法的时候,让C代码调用
     * public void helloFromJava()
     */
    public native void callbackHelloFromJava();

    /**
     * 当执行这个方法的时候,让C代码调用
     * public void printString(String s)
     */
    public native void callbackPrintString();

    /**
     * 当执行这个方法的时候,让C代码调用
     * public static void sayHello(String s)
     */
    public native void callbackSayHello();

    public int add(int x, int y) {
        Log.e("TAG", "add()  x=" + x + " y=" + y);
        return x + y;
    }

    public void helloFromJava() {
        Log.e("TAG", "helloFromJava");
    }

    public void printString(String s) {
        Log.e("TAG", "C中输入的:" + s);
    }

    public static void sayHello(String s) {
        Log.e("TAG", "我是java代码中的JNI" +
                ".java中的sayHello(String s)静态方法,我被C调用了:" + s);
    }
}

2.根据命令行,javah +JNI3全类名,生成CCallJava.c对应的头文件
com_example_jniproject_JNI3.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_example_jniproject_JNI3 */

#ifndef _Included_com_example_jniproject_JNI3
#define _Included_com_example_jniproject_JNI3
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jniproject_JNI3
 * Method:    callbackAdd
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackAdd
  (JNIEnv *, jobject);

/*
 * Class:     com_example_jniproject_JNI3
 * Method:    callbackHelloFromJava
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackHelloFromJava
  (JNIEnv *, jobject);

/*
 * Class:     com_example_jniproject_JNI3
 * Method:    callbackPrintString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackPrintString
  (JNIEnv *, jobject);

/*
 * Class:     com_example_jniproject_JNI3
 * Method:    callbackSayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackSayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

3.对应的CCallJava.c文件中的代码如下

//
// Created by DELL on 2023/8/6.
//
#include "com_example_jniproject_JNI3.h"
#include 
#include 

#include 

#define TAG "luyu"
#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类型


/**
 * 让C代码调用Java中JNI类的public int add(int x ,int y)
 */
JNIEXPORT void Java_com_example_jniproject_JNI3_callbackAdd(JNIEnv *env, jobject jobj) {
    //1.得到字节码
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");
    //2.得到方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最后一个参数是方法签名
    jmethodID jmethodId = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
    //3.实例化该类
    jobject jobject1 = (*env)->AllocObject(env, jclazz);
    //4.调用方法
    //jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint value = (*env)->CallIntMethod(env, jobject1, jmethodId, 99, 1);
    //成功调用
    LOGE("value==%d\n", value);
};

/***
 * 让C代码调用
 * public void helloFromJava()
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackHelloFromJava
        (JNIEnv *env, jobject jobj) {
    //1.得到字节码
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");
    //2.得到方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最后一个参数是方法签名
    jmethodID jmethodIds = (*env)->GetMethodID(env, jclazz, "helloFromJava", "()V");
    //3.实例化该类
    jobject jobject1 = (*env)->AllocObject(env, jclazz);
    //4.调用方法
    //jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    (*env)->CallVoidMethod(env, jobject1, jmethodIds);
};

/***
 * 让C代码调用
 * public void printString(String s)
 *
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackPrintString
        (JNIEnv *env, jobject jobj) {
    //1.得到字节码
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");
    //2.得到方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最后一个参数是方法签名
    jmethodID jmethodIds = (*env)->GetMethodID(env, jclazz, "printString", "(Ljava/lang/String;)V");
    //3.实例化该类
    jobject jobject1 = (*env)->AllocObject(env, jclazz);
    //4.调用方法
    //jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    // jstring     (*NewStringUTF)(JNIEnv*, const char*);
    jstring jst = (**env).NewStringUTF(env, "I am Android!");
    (*env)->CallVoidMethod(env, jobject1, jmethodIds, jst);
};

/**
 * 让C代码调用
 * public static void sayHello(String s)
 *
 */
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackSayHello
        (JNIEnv *env, jobject jobj){
    //1.得到字节码
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");
    //2.得到方法
    // jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最后一个参数是方法签名
    jmethodID jmethodIds = (*env)->GetStaticMethodID(env, jclazz, "sayHello", "(Ljava/lang/String;)V");
    jstring jst = (**env).NewStringUTF(env, "I am Android!");
    (*env)->CallStaticVoidMethod(env,jclazz,jmethodIds,jst);
};

之后,在MainActivity中调用对应的方法即可

public class MainActivity extends AppCompatActivity {
    private JNI2 jin2;
    private static final String TAG = "MainActivity";
    private JNI3 mJNI3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jin2 = new JNI2();
        mJNI3 = new JNI3();
    }

    /******以下是Java代码调用C*****/
    public void checkPwd(View view) {
        int result = jin2.checkPwd("12345678");
        Log.i(TAG, "result==" + result);
    }

    public void increaseArrayEles(View view) {
        int array[] = {1, 2, 3, 4};
        jin2.increaseArrayEles(array);
        for (int i = 0; i < array.length; i++) {
            Log.i(TAG, "result[" + i + "]:" + array[i]);
        }
    }

    public void sayHello(View view) {
        String result = jin2.sayHello("I am from java");
        Log.i(TAG, "result==" + result);
    }

    public void add(View view) {
        int result = jin2.add(99, 1);
        Log.i(TAG, "result==" + result);
    }

    /******以下是C调Java代码*****/
    public void callbackAdd(View view) {
        mJNI3.callbackAdd();
    }

    public void callbackHelloFromJava(View view) {
        mJNI3.callbackHelloFromJava();
    }

    public void callbackPrintString(View view) {
        mJNI3.callbackPrintString();
    }

    public void callbackSayHello(View view) {
        mJNI3.callbackSayHello();
    }
}

点击按钮便可输出对应的日志,表示调用成功。

Android JNI开发从0到1,java调C,C调Java,保姆级教程详解_第3张图片

你可能感兴趣的:(android,java,c语言,jni,ndk)