NDK实战(二)—— JNI原理(下)

本文深入解析JNI原理,详细介绍了JNI方法的注册流程,从JVM_NativeLoad到JNI_OnLoad,再到Native方法的具体注册实现。通过一个动态注册的native方法实例,阐述了Java中方法定义、JNI中方法映射、gMethod定义、注册方法的编写以及在JNI_OnLoad中的调用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JNI原理(下)

一、注册JNI方法

接着上一篇,进入 JVM_NativeLoad 方法

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jstring javaLibrarySearchPath) {
    ...

  std::string error_msg;
  {
    art::JavaVMExt* vm =
    art::Runtime::Current()->GetJavaVM();
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         javaLibrarySearchPath,
                                         &error_msg);
    ...
}

该方法主要做了一件事:

调用Java虚拟机加载nativeLibrary

进入JavaVMExt中查看 LoadNativeLibrary 方法

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jstring library_path,
                                  std::string* error_msg) {
  ...
  SharedLibrary* library;
  ...
  void* handle = android::OpenNativeLibrary(env,
                                            runtime_->GetTargetSdkVersion(),
                                            path_str,
                                            class_loader,
                                            library_path,
                                            &needs_native_bridge,
                                            error_msg);

  ...
  
  bool created_library = false;
  {
  //通过创建一个 Android 的 Handle 并用其创建一个 SharedLibrary
    std::unique_ptr<SharedLibrary> new_library(
        new SharedLibrary(env,
                          self,
                          path,
                          handle,
                          needs_native_bridge,
                          class_loader,
                          class_loader_allocator));

    MutexLock mu(self, *Locks::jni_libraries_lock_);
    library = libraries_->Get(path);//通过路径获取library库
    if (library == nullptr) { 
      library = new_library.release();
      libraries_->Put(path, library);//将库放入 library 集合中,具体个数为动态链接库的个数
      created_library = true;
    }
  }
  ...
  //重点:
   void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
   if (sym == nullptr) {
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
  } else {
    
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
   
    //在我们要加载so库中查找JNI_OnLoad方法,如果没有系统就认为是静态注册方式进行的,直接返回true,代表so库加载成功。
    //如果找到JNI_OnLoad就会调用JNI_OnLoad方法,JNI_OnLoad方法中一般存放的是方法注册的函数。
    //所以如果采用动态注册就必须要实现JNI_OnLoad方法,否则调用java中声明的native方法时会抛出异常。
    
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);

    
    int version = (*jni_on_load)(this, nullptr);

    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
      // Make sure that sigchain owns SIGSEGV.
      EnsureFrontOfChain(SIGSEGV);
    }

    self->SetClassLoaderOverride(old_class_loader.get());

    //对 version 进行判断
    if (version == JNI_ERR) {
      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
    } else if (JavaVMExt::IsBadJniVersion(version)) {
      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
                    path.c_str(), version);
    } else {
      was_successful = true;
    }
}
bool JavaVMExt::IsBadJniVersion(int version) {
  // We don't support JNI_VERSION_1_1. These are the only other valid versions.
  return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
}

该方法主要就是:

  1. 创建 Android 中的 Handle,并用 Handle 再创建一个 SharedLibrary ,并将其放入一个 SharedLibrary 的集合中
  2. 通过 SharedLibraryFindSymbol(“JNI_OnLoad”, nullptr) 找到 JNI_Onload 方法并赋值给 void* 类型的 sym
  3. sym 强转为 JNI_OnLoadFn 的方法指针(JNI_OnLoadFn 是 (JavaVM*, void*) 的方法别名)
  4. 通过 JNI_OnLoadFn 获取一个 int 类型的变量 version
  5. version 进行判断,若 version 等于 JNI_ERR(-1) 或不等于 JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6 中的任意一个,即为错误的。

二、Native方法的注册流程

打开Android源码中的MediaPlayer类,其中有

 private native final void native_setup(Object mediaplayer_this);

其具体实现为 android_media_MediaPlayer.cpp 中的

static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)

类中还有一个 gMethod

static const JNINativeMethod gMethods[] = {
    ...
    
    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
   
    ...
};

三个参数分别对应着

  • Java中的方法
  • Java中方法参数及返回值签名
  • JNI中的方法

接下去寻找 gMethod 的调用,发现其被用于 register_android_media_MediaPlayer 方法中进行注册

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

static int register_android_media_MediaPlayer(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}

register_android_media_MediaPlayer 的调用是在
JNI_OnLoad

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_MediaPlayer(env) < 0) {
        ALOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    ...
    
    //此处的返回值与上文所说的三个正确返回值相对应
    result = JNI_VERSION_1_4;

bail:
    return result;
}

现在,一连串的源码分析就走通了:

  1. LoadNativeLibrary 方法中,SharedLibrary会通过 FindSymbol 调用 JNI_OnLoad
  2. JNI_OnLoad 会将 native 方法进行注册,并返回一个 version
  3. versionJNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6 中的一个则表示注册成功

三、Native 方法注册的具体实现

接着刚刚 register_android_media_MediaPlayer 方法,继续寻找 registerNativeMethods 的实现

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

又是一个方法调用,继续找 jniRegisterNativeMethods 的实现,这个方法在 JNIHelp.cpp

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    //findClass 之后 赋值给 c
    scoped_local_ref<jclass> c(env, findClass(env, className));
    ...

    //c.get()就是获取刚刚 findClass 的类
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0)
    
    ...
    
}

最终注册就是在 RegisterNatives

四、手写一个动态注册的 native 方法

1. 在Java中定义一个方法
public static native void diff(String path, String patternPath, int fileNum);
2. 在JNI中定义对应的方法
JNIEXPORT void JNICALL
jni_diff(JNIEnv *env, jclass type, jstring path_,
     jstring patternPath_, jint fileNum) {
    const char *path = (*env)->GetStringUTFChars(env, path_, 0);
    const char *patternPath = (*env)->GetStringUTFChars(env, patternPath_, 0);
    LogI("JNI Begin");

    (*env)->ReleaseStringUTFChars(env, path_, path);
    (*env)->ReleaseStringUTFChars(env, patternPath_, patternPath);
}
3. 定义一个 gMethod
static const JNINativeMethod gMethods[] = {
        {"diff", "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) jni_diff},
};
4. 定义一个注册方法
static int registerNative(JNIEnv *env) {
    LogI("register native start");
    jclass jclz = (*env)->FindClass(env, "yourpackage/className");
    if (jclz == NULL) {
        LogI("class is null");
    }

    if ((*env)->RegisterNatives(env, jclz, gMethods, NELEM(gMethods)) < 0) {
        LogI("register failed");
    };
    LogI("register native success");
    return JNI_TRUE;
}
5. 在JNI_OnLoad中实现注册方法
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;

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

    if (registerNative(env) < 0) {
        LogI("ERROR: native registration failed");
        goto bail;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;
    LogI("register native completed");
    bail:
    return result;
}
6. 运行打印结果
I/TAG: register native start
I/TAG: register native success
I/TAG: register native completed
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值