实现原理

原理其实很简单,就是利用了反射。

实现步骤

第一步:新建一个 JNI 类(名字可以自己随便取)

如何利用jni实现C调java_jni

第二步:native 方法的编写

package com.wust.ccallj;

public class cCallJava {
    public native void callPrintString();
    
    public void printString(String s){
        System.out.println("我被C语言调用了,他传来了:" + s);
    }
}

如何利用jni实现C调java_android_02

第三步:新建 cpp 文件夹

这个cpp文件夹可以自己从头开始建,也可以用集成开发工具创建 jni入门级别教程,这次我们自己从零创建

如何利用jni实现C调java_android_03

第四步:生成 cCallJava 的 .h 文件,并放于 cpp 文件夹下

生成头文件时 报的 错误可以忽略

如何利用jni实现C调java_java_04

出现下面这个警告 是因为 我们没有配置 cMakeLists.txt 所以不用紧张

如何利用jni实现C调java_ide_05

第五步:编写对应的 .cpp 源文件

可以发现,我们建的 .cpp 文件也是报这个警告,下面我们就通过配置  cMakeLists.txt 让他彻底消失

如何利用jni实现C调java_android_06

 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.

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.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             # 设置生成.so 的文件名,也是你在 java 代码里调用的名字,填一个就好了,记住!!!
             cCallJava

             # Sets the library as a shared library.
             #设置库的类型  一种静态文件  STATIC .a   一种动态文件  SHARED .so
             SHARED

             # Provides a relative path to your source file(s).
             # 需要编译的c/c++ 文件,这里填相对路径
             testCallJava.cpp)

# 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.
                       # 指定链接的目标库
                       cCallJava

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

同时,在 build.gradle 里关联到 cMakeLists.txt,右键  cMakeLists.txt,点击我圈出来的东西

如何利用jni实现C调java_反射_07

选择该 cMakeLists.txt 对应的路径

如何利用jni实现C调java_android_08

如何利用jni实现C调java_java_09

你会发现 build.gradle 文件中多这么个配置

如何利用jni实现C调java_jni_10

其实这个你也可以手写进去,如果你记得的话

externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
        }
    }

这个时候你就会发现那个警告没了,头文件只是起代码提示作用,就让他红着吧(报错原因是 jni.h 文件没找到),我们开始编写 .cpp 文件

如何利用jni实现C调java_jni_11

//
// Created by yiqi on 2021/4/3.
//

#include <jni.h>

extern "C" {
/*
 * Class:     com_wust_ccallj_cCallJava
 * Method:    callPrintString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_wust_ccallj_CallJava_callPrintString
(JNIEnv * env, jobject jobj){
    //1、获取字节码
    jclass jclazz = (*env).FindClass("com/wust/ccallj/aCallJava");
    //2、获取方法
    jmethodID jmethodId = (*env).GetMethodID(jclazz,"printString","(Ljava/lang/String;)V");
    //3、实例化对象
    jobject jobject1 = (*env).AllocObject(jclazz);
    //4、调用方法
    jstring jstr = (*env).NewStringUTF("这句话来自C");
    (*env).CallVoidMethod(jobject1,jmethodId,jstr);
};

}

注意,如果大家和我的类名一样,首字母小写了,它就会报下面这个错误,把首字母改成大写即可 

如何利用jni实现C调java_java_12

//
// Created by yiqi on 2021/4/3.
//

#include <jni.h>

extern "C" {
/*
 * Class:     com_wust_ccallj_cCallJava
 * Method:    callPrintString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_wust_ccallj_CallJava_callPrintString
(JNIEnv * env, jobject jobj){
    //1、获取字节码
    jclass jclazz = (*env).FindClass("com/wust/ccallj/CallJava");
    //2、获取方法
    jmethodID jmethodId = (*env).GetMethodID(jclazz,"printString","(Ljava/lang/String;)V");
    //3、实例化对象
    jobject jobject1 = (*env).AllocObject(jclazz);
    //4、调用方法
    jstring jstr = (*env).NewStringUTF("这句话来自C");
    (*env).CallVoidMethod(jobject1,jmethodId,jstr);
};

}

第六步:静态引入库,并调用

package com.wust.ccallj;

public class CallJava {
    static {
        System.loadLibrary("cCallJava");
    }
    public native void callPrintString();

    public void printString(String s){
        System.out.println("我被C语言调用了,他传来了:" + s);
    }
}

如何利用jni实现C调java_jni_13

package com.wust.ccallj;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CallJava callJava = new CallJava();
        callJava.callPrintString();
    }
}

如何利用jni实现C调java_jni_14

效果展示

完美结束

如何利用jni实现C调java_反射_15