Android JNI入门:从基础注册到集成第三方So库及源码分析

566 阅读8分钟

前言

一般App开发中,其实挺少会用到JNI,但阅读源码或集成so会接触到它,因此还是需要有所了解。本文将介绍JNI的注册、集成so以及Android源码中的JNI。

本文默认读者掌握JNI基础语法,需要语法参考请访问项目文档

一、JNI是什么

JNI就是为了让Java层和Native层代码可以相互调用。

而JNI分为静态注册和动态注册2种,话不多说,我们直接开始正题吧。

二、静态注册

好处:在项目中直接将 Java 方法绑定到固定的 Native 函数,可以减少运行时查找开销。

2.1 运行Android JNI 示例demo

在Android Studio中,新建一个Native C++项目

选择Native C++,如下图所示

image.png

我们先分析一下自动生成的Native C++项目,打开它。 生成的目录结构中,cpp 目录包含 CMakeLists.txtnative-lib.cpp 文件,其中 CMakeLists.txt 定义了动态库的构建流程。

项目目录结构如下图所示:

图1:Android Studio新建Native C++项目结构

打开CMakeLists.txt

# 声明使用的CMake最低版本。
# Android Studio里指定这个版本要和build.gradle中externalNativeBuild.cmake.version保持一致。
cmake_minimum_required(VERSION 3.22.1)

# 设置当前CMake工程名为data,这个名字可以随便起,通常和模块或目标库名字有关。
project("data")

# 告诉CMake编译一个名为data的共享库(.so),它包含源文件native-lib.cpp。
# SHARED 表示生成.so库(动态库);如果写 STATIC 则会生成.a(静态库)。
# 注意:${CMAKE_PROJECT_NAME}是上面project("data")定义的变量,相当于写add_library(data SHARED native-lib.cpp)
add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp)

# 给data这个库链接上Android的两个系统库
target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log)

再打开native-lib.cpp文件

#include <jni.h>         // JNI 头文件,提供 Java/C++ 交互相关的接口类型和函数
#include <string>        // C++ 标准库中的 string 类型

// 使用 C 语言方式导出符号(避免 C++ 名字修饰)
// JNIEXPORT:告诉编译器导出这个函数供 Java 调用
// JNICALL:定义 JNI 调用规范
extern "C" JNIEXPORT jstring JNICALL
Java_com_jni_data_MainActivity_stringFromJNI( // 这个名字必须和 Java 中的方法签名匹配
        JNIEnv* env,       // JNIEnv:Java 和 Native 之间的桥梁,提供操作 Java 对象的方法
        jobject /* this */) { // jobject:调用这个 native 方法的 Java 对象(通常是 this)

    std::string hello = "Hello from C++";  // 使用 C++ 字符串构造内容

    // 将 C++ string 转为 JNI 的 jstring 类型(Java 的 String)
    return env->NewStringUTF(hello.c_str());
}

再看看MainActivity

package com.jni.data

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.jni.data.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Example of a call to a native method
        binding.sampleText.text = stringFromJNI()
    }

    /**
     * A native method that is implemented by the 'data' native library,
     * which is packaged with this application.
     */
    external fun stringFromJNI(): String

    companion object {
        // Used to load the 'data' library on application startup.
        init {
            System.loadLibrary("data")
        }
    }
}

运行后界面如下图所示,成功输出了来自 native 层的字符串,这验证了 JNI 方法与 Java 层绑定的正确性

整个流程

  1. Gradle 调用您的外部构建脚本 CMakeLists.txt
  2. CMake 按照构建脚本中的命令将 C++ 源代码文件 native-lib.cpp 编译到共享对象库中,并将其命名为 libnative-lib.so。Gradle 随后会将其打包到应用中。
  3. 在运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库。现在,应用就可以使用库的原生函数 stringFromJNI() 了。
  4. MainActivity.onCreate() 调用 stringFromJNI(),后者会返回 "Hello from C++",并使用它来更新 TextView

详见developer.android.google.cn/studio/proj…

2.2 实现静态注册

其实上面的例子就是静态注册,咱们照猫画虎,也实现一个自己的JNI

静态注册的方法名:Java+包名+方法所在类名+方法名,单词之间用下划线连接。

比如这里包名为com.jni.data,类名是MainActivity,方法名是intFromJNI,那么静态注册的方法名就是Java_com_jni_data_MainActivity_intFromJNI

在cpp目录下,新建一个C++文件,文件名随意,这里我命名为my-static-lib

#include<JNI.h>

extern "C" JNIEXPORT jint JNICALL
Java_com_jni_data_MainActivity_intFromJNI(JNIEnv* env, jobject /* this */) {
    int number = 2;
    return static_cast<jint>(number);
}

修改CMakeLists.txt

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        my-static-lib.cpp   // 文件名.cpp
        native-lib.cpp)

修改MainActivity

    ...
    binding.sampleText.text = "${intFromJNI()}" // 1.调用intFromJNI获取到数字
    ...

    external fun intFromJNI(): Int // 2.声明intFromJNI函数

    companion object {
        // Used to load the 'data' library on application startup.
        init {
            System.loadLibrary("data")
        }
    }
}

运行,界面会显示2

至此我们静态注册已经实现,但还可以进一步优化,可以把MainActivity关于JNI注册相关代码移到另外一个类中。

package com.jni.data

class JniHelper {
    companion object {
        // Used to load the 'data' library on application startup.
        init {
            System.loadLibrary("data")
        }

        @JvmStatic
        external fun stringFromJNI(): String

        @JvmStatic
        external fun intFromJNI(): Int
    }
}

因为stringFromJNI和intFromJNI都移动到了JniHelper, 需要修改它们的方法名

native-lib.cpp

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

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

my-static-lib.cpp:

#include<JNI.h>

extern "C" JNIEXPORT jint JNICALL
Java_com_jni_data_JniHelper_intFromJNI(JNIEnv* env, jclass ) {
    int number = 2;
    return static_cast<jint>(number);
}

删除MainActivity中多余的代码并修改代码

binding.sampleText.text = JniHelper.stringFromJNI()

运行,同样的效果

三、动态注册

好处:在大型项目中降低方法名变更带来的维护成本,并在 JNI_OnLoad 阶段集中注册。

3.1 实现动态注册

同静态注册一样,在cpp目录下新建C++文件,命名随意, 这里我命名为my-shared-lib

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

    jstring sharedLibString(JNIEnv* env, jobject /* this */) {
        std::string hello = "Hello from shared lib";
        return env->NewStringUTF(hello.c_str());
    }

    # 动态注册
    jint registerMethod(JNIEnv* env) {
        # 查找 Java 层的类 com.jni.data.JniHelper,以便向它注册 native 方法。
        jclass clz = env->FindClass("com/jni/data/JniHelper");
        # 定义一个 native 方法映射表 `JNINativeMethod` 参数作用分别是Java 中 native 方法名,方法签名,表示无参数、返回 `String`,指向实际的 C++ 实现函数指针
        JNINativeMethod jniNativeMethod[] = {{"sharedLibString", "()Ljava/lang/String;", (void*) sharedLibString}};
        # 调用`RegisterNatives 注册 native 方法到对应 Java 类。
        return env->RegisterNatives(clz, jniNativeMethod, sizeof(jniNativeMethod) / sizeof(jniNativeMethod[0]));
    }

    # JNI_OnLoad 是在 .so 库被 System.loadLibrary() 加载时自动调用的函数。
    # 用于初始化 JNI 环境,并进行 native 方法注册。返回 JNI 版本(如 `JNI_VERSION_1_6`)表示加载成功。如果出错,返回 `JNI_ERR`。
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env;
        if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
            return JNI_ERR;
        }
        jint result = registerMethod(env);
        return JNI_VERSION_1_6;
    }

修改CMakeLists.txt

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        my-static-lib.cpp
        my-shared-lib.cpp
        native-lib.cpp)

修改JniHelper

class JniHelper {
    companion object {
        // Used to load the 'data' library on application startup.
        init {
            System.loadLibrary("data")
        }

        @JvmStatic
        external fun stringFromJNI(): String

        @JvmStatic
        external fun intFromJNI(): Int

        @JvmStatic
        external fun sharedLibString(): String
    }
}

修改MainActivity

    ...
    binding.sampleText.text = JniHelper.sharedLibString()
    ...
}

运行项目,界面显示文字为sharedLibString的返回值Hello from shared lib

静态/动态注册步骤:

1.编写对应的库文件

2.修改CMakeLists.txt

3.修改需要用到这个库的文件

3.2 补充说明

1.编译生成so文件,在app/build/intermediates/cxx/debug|release//obj//libxxx.so下面

image.png

2.如何确认生成的so文件已经在apk中呢,在Android Studio中打开编译生成的apk

image.png

上图中的libdata.so就是我们生成的so文件,已经在apk中了,这个so的文件名,是lib加上CMakeLists.txt文件中project("xxx")中的xxx,我这里的是data,所以就是libdata.so了

四、集成第三方so库

我们学会了如何静态和动态注册。那现在有一个so库,比如一个加密字符串的库,如何在项目中使用呢

新建一个C++ native项目,在app/main下新建目录jniLibs,将之前app/build/intermediates/cxx/debug|release//obj/arm64/,整个文件夹复制到新项目的jniLibs目录下,还需复制之前项目JniHelper文件到新项目的MainActivity同级目录

image.png

注意 新项目中的JniHelper的包名需要与之前项目包名一致 ,复制过来时,会导致包名变更,手动修改为之前包名

package com.jni.second

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.jni.data.JniHelper
import com.jni.second.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Example of a call to a native method
        binding.sampleText.text = JniHelper.sharedLibString()
    }
}
// 此处是生成so文件项目的包名
package com.jni.data

class JniHelper {
    companion object {
        // Used to load the 'data' library on application startup.
        init {
            System.loadLibrary("data")
        }

        @JvmStatic
        external fun stringFromJNI(): String

        @JvmStatic
        external fun intFromJNI(): Int

        @JvmStatic
        external fun sharedLibString(): String
    }
}

再次提醒:当前项目包名是com.jni.second,之前项目是com.jni.data, 新项目中的JniHelper包名需要同之前项目的,即com.jni.data

运行后界面如下图所示,成功输出了来自 so库的字符串,这验证了已成功集成第三方so库

image.png

五、native调用Kotlin/Java方法

在3,4小节,我们都是通过Jni让Java/Kotlin层去调用native层,来看看怎么通过Jni让native层调用Java/Kotlin

5.1 新建一个数据类Student

data class Student(var name: String, var age: Int)

需要注意的是参数如果定义为val时,是不会有setter方法的

5.2 修改JniHelper

class JniHelper {
    companion object {
        ...

        @JvmStatic
        external fun updatePerson(person: Person)

        @JvmStatic
        external fun getPerson(): Person

        @JvmStatic
        external fun updateStudent(student: Student)

        @JvmStatic
        external fun getStudent(): Student
    }
}

5.3 修改my-shared-lib

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

jstring sharedLibString(JNIEnv* env, jclass /* this */) {
    std::string hello = "Hello from shared lib";
    return env->NewStringUTF(hello.c_str());
}

void updateStudent(JNIEnv *env, jclass clazz, jobject student) {
    //获取person的class对象
    jclass sJclass = env->GetObjectClass(student);
    // --- Name 相关处理 ---
    jmethodID getNameMethod = env->GetMethodID(sJclass, "getName", "()Ljava/lang/String;");
    auto originalName = (jstring)env->CallObjectMethod(student, getNameMethod);
    const char *c_name = env->GetStringUTFChars(originalName, nullptr);
    jstring name = env->NewStringUTF(c_name);
    jmethodID setNameMethod = env->GetMethodID(sJclass, "setName", "(Ljava/lang/String;)V");
    env->CallVoidMethod(student, setNameMethod, name);
    env->ReleaseStringUTFChars(originalName, c_name);

    // --- Age 相关处理 ---
    jmethodID getAgeMethod = env->GetMethodID(sJclass, "getAge", "()I");
    jint age = env->CallIntMethod(student, getAgeMethod);
    jmethodID setAgeMethod = env->GetMethodID(sJclass, "setAge", "(I)V");
    env->CallVoidMethod(student, setAgeMethod, age);
}

jobject getStudent(JNIEnv *env, jclass clazz) {
    jclass jclass1 = env->FindClass("com/jni/data/model/Student");
    // 数据类主构造函数签名需要与Kotlin定义完全一致
    // 假设Student定义为: data class Student(val age: Int, val name: String)
    // jmethodID jmethodID1 = env->GetMethodID(jclass1, "<init>", "(ILjava/lang/String;)V");

    // 如果参数顺序不同(如name在前),需要调整签名和传参顺序:
     jmethodID jmethodID1 = env->GetMethodID(jclass1, "<init>", "(Ljava/lang/String;I)V");

    jint age = 26;
    jstring str = env->NewStringUTF("xiaoming");
    jobject student = env->NewObject(jclass1, jmethodID1, str, age);
    return student;
}

jint registerMethod(JNIEnv* env) {
    jclass clz = env->FindClass("com/jni/data/JniHelper");
    JNINativeMethod jniNativeMethod[] = {
            {"updateStudent", "(Lcom/jni/data/model/Student;)V", (void *) updateStudent},
            {"getStudent", "()Lcom/jni/data/model/Student;", (void *) getStudent},
    };

    return env->RegisterNatives(clz, jniNativeMethod, sizeof(jniNativeMethod) / sizeof(jniNativeMethod[0]));
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }
    jint result = registerMethod(env);
    return JNI_VERSION_1_6;
}

5.4 修改布局文件和Mactivity

新增两个按钮,用来调用这两个方法,新增两个输入框用来输入名字和年龄

...
binding.sampleText.text = JniHelper.sharedLibString()
binding.setStudent.setOnClickListener {
    if (binding.inputStudentName.text.isEmpty()) {
        Toast.makeText(this, "name is empty", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }
    if (binding.inputStudentAge.text.isEmpty() ) {
        Toast.makeText(this, "age is empty", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }
    val student = Student(binding.inputStudentName.text.toString(), binding.inputStudentAge.text.toString().toInt())
    JniHelper.updateStudent(student)
    binding.sampleText.text = buildString {
        append("S name: ")
        append(student.name)
        append(" | ")
        append("age: ")
        append(student.age)
    }
}
binding.getStudent.setOnClickListener {
    val student = JniHelper.getStudent()
    binding.sampleText.text = buildString {
        append("S name: ")
        append(student.name)
        append(" | ")
        append("age: ")
        append(student.age)
    }
}
...

运行项目,输入名字和年龄,点击按钮,正常运行,且能正常修改/返回信息。验证了native成功调用Kotlin/Java方法

image.png

六、Android源码中的Jni

Android源码中有很多的Jni,我们就找一个静态和一个动态注册的例子看看

6.1 源码中的静态注册

在Handler流程中会去调用MessageQueue中的nativePollOnce,nativeWake方法。

image.png

我们去找找它的实现,根据第3小节,我们知道JNI分为静态与动态注册,先假定它是静态注册,直接搜索android_os_messagequeue_native

image.png cs.android.com/android/pla…

上面链接如果看不了,可以看这个的Android10的源码 android_os_MessageQueue.cpp - Android社区 - https://www.androi…

6.2 源码中的动态注册

在frameworks/base/media/jni/android_media_MediaPlayer.cpp下面注册了很多媒体播放相关的Jni函数

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_media_ImageWriter(env) != JNI_OK) {
        ALOGE("ERROR: ImageWriter native registration failed");
        goto bail;
    }

    if (register_android_media_ImageReader(env) < 0) {
        ALOGE("ERROR: ImageReader native registration failed");
        goto bail;
    }

    if (register_android_media_JetPlayer(env) < 0) {
        ALOGE("ERROR: JetPlayer native registration failed");
        goto bail;
    }

    if (register_android_media_MediaPlayer(env) < 0) {
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaRecorder(env) < 0) {
        ALOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }
    ...
}

我们打开register_android_media_MediaRecorder方法看看

frameworks/base/media/jni/android_media_MediaRecorder.cpp

...
static const JNINativeMethod gMethods[] = {
    {"setCamera",            "(Landroid/hardware/Camera;)V",    (void *)android_media_MediaRecorder_setCamera},
    {"setVideoSource",       "(I)V",                            (void *)android_media_MediaRecorder_setVideoSource},
    {"setAudioSource",       "(I)V",                            (void *)android_media_MediaRecorder_setAudioSource},
    {"setPrivacySensitive",  "(Z)V",                            (void *)android_media_MediaRecorder_setPrivacySensitive},
    {"isPrivacySensitive",  "()Z",                             (void *)android_media_MediaRecorder_isPrivacySensitive},
    {"setOutputFormat",      "(I)V",                            (void *)android_media_MediaRecorder_setOutputFormat},
    {"setVideoEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setVideoEncoder},
    {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},
    {"setParameter",         "(Ljava/lang/String;)V",           (void *)android_media_MediaRecorder_setParameter},
    {"_setOutputFile",       "(Ljava/io/FileDescriptor;)V",     (void *)android_media_MediaRecorder_setOutputFileFD},
    {"_setNextOutputFile",   "(Ljava/io/FileDescriptor;)V",     (void *)android_media_MediaRecorder_setNextOutputFileFD},
    {"setVideoSize",         "(II)V",                           (void *)android_media_MediaRecorder_setVideoSize},
    {"setVideoFrameRate",    "(I)V",                            (void *)android_media_MediaRecorder_setVideoFrameRate},
    {"setMaxDuration",       "(I)V",                            (void *)android_media_MediaRecorder_setMaxDuration},
    {"setMaxFileSize",       "(J)V",                            (void *)android_media_MediaRecorder_setMaxFileSize},
    {"_prepare",             "()V",                             (void *)android_media_MediaRecorder_prepare},
    {"getSurface",           "()Landroid/view/Surface;",        (void *)android_media_MediaRecorder_getSurface},
    {"getMaxAmplitude",      "()I",                             (void *)android_media_MediaRecorder_native_getMaxAmplitude},
    {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
    {"stop",                 "()V",                             (void *)android_media_MediaRecorder_stop},
    {"pause",                "()V",                             (void *)android_media_MediaRecorder_pause},
    {"resume",               "()V",                             (void *)android_media_MediaRecorder_resume},
    {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
    {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
    {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V",
                                                                (void *)android_media_MediaRecorder_native_setup},
    {"native_finalize",      "()V",                             (void *)android_media_MediaRecorder_native_finalize},
    {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },

    {"native_getMetrics",    "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},

    {"native_setInputDevice", "(I)Z",                           (void *)android_media_MediaRecorder_setInputDevice},
    {"native_getRoutedDeviceIds", "()[I",
         (void *)android_media_MediaRecorder_getRoutedDeviceIds},
    {"native_enableDeviceCallback", "(Z)V",                     (void *)android_media_MediaRecorder_enableDeviceCallback},

    {"native_getActiveMicrophones", "(Ljava/util/ArrayList;)I", (void *)android_media_MediaRecord_getActiveMicrophones},
    {"native_getPortId", "()I", (void *)android_media_MediaRecord_getPortId},
    {"native_setPreferredMicrophoneDirection", "(I)I",
            (void *)android_media_MediaRecord_setPreferredMicrophoneDirection},
    {"native_setPreferredMicrophoneFieldDimension", "(F)I",
            (void *)android_media_MediaRecord_setPreferredMicrophoneFieldDimension},
};

// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}

可以看到register_android_media_MediaRecorder调用了AndroidRuntime::registerNativeMethods,再看看 AndroidRuntime::registerNativeMethods又做了什么

frameworks/base/core/jni/platform/host/HostRuntime.cpp

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
                                                     const JNINativeMethod* gMethods,
                                                     int numMethods) {
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

接着跟

libnativehelper/include/nativehelper/JNIHelp.h

[[maybe_unused]] static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
                                                     const JNINativeMethod* methods,
                                                     int numMethods) {
    using namespace android::jnihelp;
    jclass clazz = env->FindClass(className);
    if (clazz == NULL) {
        __android_log_assert("clazz == NULL", "JNIHelp",
                             "Native registration unable to find class '%s'; aborting...",
                             className);
    }
    int result = env->RegisterNatives(clazz, methods, numMethods); // 1
    env->DeleteLocalRef(clazz);
    if (result == 0) {
        return 0;
    }

    // Failure to register natives is fatal. Try to report the corresponding exception,
    // otherwise abort with generic failure message.
    jthrowable thrown = env->ExceptionOccurred();
    if (thrown != NULL) {
        struct ExpandableString summary;
        ExpandableStringInitialize(&summary);
        if (GetExceptionSummary(env, thrown, &summary)) {
            __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", "%s", summary.data);
        }
        ExpandableStringRelease(&summary);
        env->DeleteLocalRef(thrown);
    }
    __android_log_print(ANDROID_LOG_FATAL, "JNIHelp",
                        "RegisterNatives failed for '%s'; aborting...", className);
    return result;
}

其他逻辑都不用看,只看代码1处,可以看到这里调用了env->RegisterNatives,所以可以得出结论ndroidRuntime::registerNativeMethods是对env->RegisterNatives 的封装。

感谢阅读,希望本文对你有所帮助,如有任何不对的地方,欢迎大家指正

注:本文的Jni代码已整合到Demo

七、参考资料

  1. 向您的项目添加 C 和 C++ 代码  |  Android Studio  |  Android Developers
  2. 零基础带你吃掉JNI全家桶(一) - 掘金
  3. Android JNI 入门JNI - 掘金