Java教程

ndk之C调用java方法以及动态注册

本文主要是介绍ndk之C调用java方法以及动态注册,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、静态注册和动态注册

ndk开发需要在java层和native层相互调用代码,如何确定native方法与jni函数之间的映射关系呢?这就涉及到jni函数的注册,注册方式有两种:静态注册和动态注册。
静态注册采用基于约定的命名规则(Java_开头,后接类的全限定名加下划线,方法名这三个组成部分组成,如下代码所示),可以通过javah或IDE自动生成native方法对应的函数声明。
优点是简单;缺点是不灵活,修改java类名或jni方法名时,需要同步修改对应的native函数命名。

extern "C"
JNIEXPORT jint JNICALL
Java_com_shan_dynamicndk_MainActivity_jniCallAdd(JNIEnv *env, jobject thiz, jint num1, jint num2) {
    // TODO: implement jniCallAdd()
}

动态注册通过JNINativeMethod函数和JNI_OnLoad函数的编辑可以在native代码中自由定义jni函数与native函数的映射。优点是灵活,缺点可能会由于无法使用javah和IDE自动补全功能而相对麻烦些。

二、C代码调用java代码(静态注册)

以native层调用java层方法获取uuid的方法为例
java层代码MainActivity.java如下

//MainActivity.java
package com.shan.ndkapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.shan.ndkapp.databinding.ActivityMainBinding;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ndk_test_java";

    // Used to load the 'ndkapp' library on application startup.
    static {
        System.loadLibrary("ndkapp");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(getUUidFromJava());
    }


    public String getUuid() {
        String uuid = UUID.randomUUID().toString();
        Log.d(TAG, "getUuid:"+uuid);
        return uuid;
    }

    public native String getUUidFromJava();
}

native层代码native-lib.cpp如下:

//native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define  LOG_TAG  "ndk_test_c"
#define LOGD(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"
JNIEXPORT jstring JNICALL
Java_com_shan_ndkapp_MainActivity_getUUidFromJava(JNIEnv *env, jobject thiz) {
    // TODO: implement getUUidFromJava()
    jclass  j_class = env->GetObjectClass(thiz);//根据jobject获取jclass
    jmethodID j_methodId = env->GetMethodID(j_class,"getUuid","()Ljava/lang/String;");//最后一个参数方法签名可以通过javap -s className得到
    jobject j_uuid = env->CallObjectMethod(thiz, j_methodId);
    const char* uuid = env->GetStringUTFChars(static_cast<jstring>(j_uuid),NULL); //jstring转化为char*才可以在C语言打印
    LOGD("Java_com_shan_ndkapp_MainActivity_getUUidFromJava,uuid=%s",uuid);
    env->ReleaseStringUTFChars(static_cast<jstring>(j_uuid),uuid); //回收字符串
    return static_cast<jstring>(j_uuid);
}

上面native代码的第13行获取方法签名时,如果忘记了签名规则,可以在当前工程目录下面找到
build/intermediates/javac/debug/classes/com/shan/ndkapp/MainActivity.class,在该目录下打开命令行,通过javap -s MainActivity.class获取该类所有签名信息。

三、动态注册

以java层调用native层代码获取字符串以及native层代码调用java层add函数为例,这两个函数都采用动态注册实现。
java代码MainActivity.java如下

//MainActivity.java
package com.shan.dynamicndk;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.shan.dynamicndk.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'dynamicndk' library on application startup.
    static {
        System.loadLibrary("dynamicndk");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI()+":"+jniCallAdd(2,3));
    }

    public int add(int num1,int num2) {
        return num1+num2;
    }

    /**
     * A native method that is implemented by the 'dynamicndk' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
    public native int jniCallAdd(int num1,int num2);
}

native层代码如下

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
c_getString(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT jint JNICALL
c_callAdd(
        JNIEnv* env,
        jobject  thiz,jint num1, jint num2) {
    jclass  j_class = env->GetObjectClass(thiz);//根据jobject获取jclass
    jmethodID j_methodId = env->GetMethodID(j_class,"add","(II)I");//最后一个参数方法签名可以通过javap -s className得到
    jint j_add = env->CallIntMethod(thiz, j_methodId,num1,num2); //调用java层的add方法
    return j_add;
}

static JNINativeMethod methods[] = {
        { "stringFromJNI", "()Ljava/lang/String;", (void*)c_getString},
        { "jniCallAdd", "(II)I", (void*)c_callAdd},
};

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv *env = NULL;

    // 获取JNI env变量
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        // 失败返回-1
        return JNI_ERR;
    }

    // 获取native方法所在类
    jclass clazz = env->FindClass("com/shan/dynamicndk/MainActivity"); //包名类名必须完全一致,否则匹配失败
    if (clazz == NULL) {
        return JNI_ERR;
    }

    // 动态注册native方法
    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }

    // 返回成功
    return JNI_VERSION_1_6;
}

动态注册分为三步:1、定义JNINativeMethod结构体确定映射关系 2、调用JNI_OnLoad函数去加载 3、具体实现native函数。
JNINativeMethod结构体中记录了java层函数名称name、函数签名signature以及native层代码的函数指针fnPtr,JNINativeMethod结构体定义在jni.h中,有兴趣的话可以查看一下jni.h文件。

//jni.h
typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

这篇关于ndk之C调用java方法以及动态注册的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!