JNI的源码实现分析

在之前的文章中,我们熟悉了JNI的代码实现,并尝试手写了一个JNI的demo,实现了Java与cpp层的沟通。但是如果仔细思考还是会有一些困惑,Java和cpp最开始究竟是如何找到对方的?那种方法的映射在系统层面是如何建立起来的。这些问题是JNI规范文档所不能告诉我们的,需要我们自己找到答案。而答案就在源码里。

JNI的背景知识补充

JNI是Java语言定义的与cpp进行沟通的接口,接口具体由不同的虚拟来实现。关于JNI有以下几个比较重要的类型:

  • JavaVM:表示Java虚拟机,进程相关
  • JNIEnv:表示JNI环境的上下文,例如注册、查找类、异常等。 线程相关
  • jclass:在JNI中表示对应的的Java类。
  • jmethodID:在JNI中表示对应的的Java类中的方法的id。
  • jfiledID:在JNI中表示对应的Java类中的属性的id。
  • thread:JNI中通过AttachCurrentThread和DetachCurrentThread方法,实现和Java线程的结合。

在Android中,JNI的接口声明在系统的libnativehelper.so的动态库中,相关源码在libnativehelper/include_jni/jni.h。而JNI的实现则在虚拟机中,比如JNI中的函数操作表的实现就在art的art/runtime/jni/jni_internal.cc中。

cpp与Java的初次联系

我们假设一个时间点,在这个时间点之前,Java与cpp没有任何联系,而在这个时间点之后,他们都知道了对方的存在,建立起了沟通机制。

这个时间点cpp代码会第一次创建JVM虚拟机,创建JNIEnv上下文环境,同时导入JNI的操作函数表,并调用Java层的一个入口方法。这个时间点就是Android开机启动过程中的zygote进程的启动。

从zygote看起

我们就从zygote进程的入口函数开始看起。

c 复制代码
// frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ...
    ...
    // 初始化AppRuntime AppRuntime继承了AndroidRuntime
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    ...
    ...
    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        // zygote进程
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }

   ...
   ...
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    if (zygote) { // zygote进程,传入的com.android.internal.os.ZygoteInit是Java类
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (!className.isEmpty()) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}

这是zygote进程的初始化程序,我们看到的AppRuntime继承自AndroidRuntime:

看一下AndroidRuntime的start方法

arduino 复制代码
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{

    // 进入start方法主要办了三件事
   ...
   ...
   // 1 加载本地库,创建虚拟机,导入JNI函数表
   // 初始化JNI的函数操作,
    JniInvocation jni_invocation;
    // 就是加载系统默认的so库并从中导入相关的接口
    jni_invocation.Init(NULL);
    JNIEnv* env; // 定义一个JNIEnv指针
    // 
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    // 虚拟机创建后的回调方法
    onVmCreated(env);

// 2,通过JNI 动态注册Android中已有的Java native 函数
    // 开始注册Android已有的Java native方法
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

 // 3 调用com.android.internal.os.ZygoteInit.main() 这个静态方法入口,从cpp进入Java层
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    // 找到java classname:com.android.internal.os.ZygoteInit
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ...
    } else {
        // 找到com.android.internal.os.ZygoteInit里面的main方法
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ...
        } else {
            //调用mian方法入口,进入Java层
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
    }
   
}

如代码注释所述,start方法主要做了三件事:

    • 创建虚拟机,配置JNI函数
    • 使用JNI动态注册Android系统已有的native方法
    • 调用com.android.internal.os.ZygoteInit的main入口,进入Java层

创建虚拟机

cpp 复制代码
/***********  frameworks/base/core/jni/AndroidRuntime.cpp ********************/

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{
   
   JavaVMInitArgs initArgs; 
    ...
    ...
    //一些虚拟机的配置
    ...

    initArgs.version = JNI_VERSION_1_4;
    initArgs.options = mOptions.editArray();
    initArgs.nOptions = mOptions.size();
    initArgs.ignoreUnrecognized = JNI_FALSE;

    // JNI_CreateJavaVM 创建Java虚拟机
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }

    return 0;
}


/***********  art/runtime/jni/java_vm_ext.cc ********************/

// art虚拟机实现
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {

  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
  // 判断下版本号,只能是是JNI_VERSION_1_2 JNI_VERSION_1_4 JNI_VERSION_1_6,不能瞎传
  if (JavaVMExt::IsBadJniVersion(args->version)) {
    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
    return JNI_EVERSION;
  }

  bool ignore_unrecognized = args->ignoreUnrecognized;
  // 创建 Runtime  具体逻辑见下面的函数 Runtime::Create
  if (!Runtime::Create(options, ignore_unrecognized)) {
    return JNI_ERR;
  }

  //把Android系统的默认库都加载进内存中
  android::InitializeNativeLoader();
  // 获取刚才创建的Runtime对象
  Runtime* runtime = Runtime::Current();
  // 调用start方法,注册runtime相关的jni方法,以及设置一些基础环境
  // 具体逻辑见下面的Runtime::Start函数
  bool started = runtime->Start();
  if (!started) {
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }
   // p_env指针指向 当前线程中获取JNIEnv
  *p_env = Thread::Current()->GetJniEnv();
  // p_vm指针指向 Runtime中获取的JavaVM
  // 此时Javavm已经创建成功
  *p_vm = runtime->GetJavaVM();
  return JNI_OK;
}



/***********  art/runtime/runtime.cc ********************/

/************************创建 Runtime  start *********************************/
bool Runtime::Create(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
  RuntimeArgumentMap runtime_options;
  //  调用Create()函数
  return ParseOptions(raw_options, ignore_unrecognized, &runtime_options) &&
      Create(std::move(runtime_options));
}
// 创建Runtime
bool Runtime::Create(RuntimeArgumentMap&& runtime_options) {
  if (Runtime::instance_ != nullptr) {
    return false;
  }
  // Runtime类很大,就不列举了,有兴趣可以去看头文件art/runtime/runtime.h
  instance_ = new Runtime; // 手动申请内存空间,创建Runtime对象
  Locks::SetClientCallback(IsSafeToCallAbort);
  // 在init逻辑里创建JavaVM的对象
  if (!instance_->Init(std::move(runtime_options))) {
    ...
    return false;
  }
  return true;
}
// 创建Java虚拟机
void Runtime::Init(){

    ...
    ...
 java_vm_ = JavaVMExt::Create(this, runtime_options, &error_msg);
  if (java_vm_.get() == nullptr) {
    LOG(ERROR) << "Could not initialize JavaVMExt: " << error_msg;
    return false;
  }

  // 把一个获取与当前线程绑定的JNIEnv对象 的钩子函数 保存起来,
  // 便于以后直接通过java_vm->GetEnv()接口来获取对应的JNIEnv
  java_vm_->AddEnvironmentHook(JNIEnvExt::GetEnvHandler);
  
  ...
  ...

}

// GetEnvHandler 钩子函数
jint JNIEnvExt::GetEnvHandler(JavaVMExt* vm, /*out*/void** env, jint version) {

  if (JavaVMExt::IsBadJniVersion(version) && version != JNI_VERSION_1_1) {
    return JNI_EVERSION;
  }
  Thread* thread = Thread::Current();
  *env = thread->GetJniEnv();
  return JNI_OK;
}

/************************创建 Runtime  end *********************************/


/************************创建 Runtime::strat  start *********************************/
// runtime::start方法
// 主要注册
bool Runtime::Start() {

...
...
  // 获取当前线程
  Thread* self = Thread::Current();
  started_ = true;

  // JNI动态注册Runtime相关的native方法
  // 具体函数逻辑见下面的RegisterRuntimeNativeMethods函数
  RegisterRuntimeNativeMethods(self->GetJniEnv());

// 创建jit
    CreateJit();

// 创建SystemClassLoader
  system_class_loader_ = CreateSystemClassLoader(this);

...
...
  return true;
}

// 执行Runtime相关的类的JNI注册
void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env) {
...
  register_dalvik_system_VMRuntime(env);
  register_java_lang_String(env); // 下面以String为例,展开具体函数逻辑
...

}


// string的native方法名,方法签名等信息
static JNINativeMethod gMethods[] = {
  FAST_NATIVE_METHOD(String, charAt, "(I)C"),
  FAST_NATIVE_METHOD(String, compareTo, "(Ljava/lang/String;)I"),
  FAST_NATIVE_METHOD(String, concat, "(Ljava/lang/String;)Ljava/lang/String;"),
  FAST_NATIVE_METHOD(String, doRepeat, "(I)Ljava/lang/String;"),
  FAST_NATIVE_METHOD(String, doReplace, "(CC)Ljava/lang/String;"),
  FAST_NATIVE_METHOD(String, fastSubstring, "(II)Ljava/lang/String;"),
  FAST_NATIVE_METHOD(String, getCharsNoCheck, "(II[CI)V"),
  FAST_NATIVE_METHOD(String, fillBytesLatin1, "([BI)V"),
  FAST_NATIVE_METHOD(String, fillBytesUTF16, "([BI)V"),
  FAST_NATIVE_METHOD(String, intern, "()Ljava/lang/String;"),
  FAST_NATIVE_METHOD(String, toCharArray, "()[C"),
};
void register_java_lang_String(JNIEnv* env) {
  REGISTER_NATIVE_METHODS("java/lang/String");
  //该宏定义 最终调用了env->RegisterNatives(c.get(), methods, method_count);完成了动态注册
}

/************************创建 Runtime::strat  end *********************************/

虚拟机创建成功之后,就为Java代码的运行提供了基础环境,为从cpp进入Java世界做好了准备

跟了上面的代码,我们了解到创建虚拟机的逻辑中就已经包含了一些JNI方法的动态注册了,不过主要是是虚拟机相关的。

动态注册系统JNI方法

紧接着第二步,注册Android系统原生的方法startReg(env)

c 复制代码
// REG_JNI宏定义
#define REG_JNI(name)      { name }
// 定义一个RegJNIRec,里面是一个函数类型的指针,函数的参数是JNIEnv指针
struct RegJNIRec {
    int (*mProc)(JNIEnv*);
};



 int AndroidRuntime::startReg(JNIEnv* env)
{
    ...
    ...
    
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);


    return 0;
}



// RegJNIRec数组,里面代表了需要注册的Java native方法
static const RegJNIRec gRegJNI[] = {
// register_com_android_internal_os_RuntimeInit 本身就是一个函数
        REG_JNI(register_com_android_internal_os_RuntimeInit),
        REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
        ...
        ...
};


// 注册函数
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        // 遍历gRegJNI数组的元素,并调用元素的方法mProc
        // 比如array[0].mProc(env) 就是调用 register_com_android_internal_os_RuntimeInit(env)
        if (array[i].mProc(env) < 0) {
            return -1;
        }
    }
    return 0;
}
// 动态注册RuntimeInit本地方法
int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
{
// 可以看到主要是为两个native方法nativeFinishInit,nativeSetExitWithoutCleanup进行注册
// 他们映射的cpp函数分别是 com_android_internal_os_RuntimeInit_nativeFinishInit  
// com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup
    const JNINativeMethod methods[] = {
            {"nativeFinishInit", "()V",
             (void*)com_android_internal_os_RuntimeInit_nativeFinishInit},
            {"nativeSetExitWithoutCleanup", "(Z)V",
             (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup},
    };
    // jniRegisterNativeMethods最终调用JNI动态注册函数 env->RegisterNatives(clazz, methods, numMethods);
    return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
        methods, NELEM(methods));
}



/**************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);
    }
    // JNI的接口注册Java函数
    int result = env->RegisterNatives(clazz, methods, numMethods);
    env->DeleteLocalRef(clazz);
    if (result == 0) {
        return 0;
    }
    ...
    ...
    return result;
}

在虚拟机创建并运行之后,Android注册了系统默认的Java native方法,此时Java的代码还未运行,这是为进入Java世界之后native方法被调用做准备。

进入Java层

在JNI函数表成功加载,虚拟机创建完毕,JNI函数注册完成之后,进入Java的逻辑其实特别简单,就是通过类的全路径名找到对应的类,然后callStaticVoidMethod,就从cpp进入了ZygoteInit.java的main方法中了。

虚拟机如何建立Java到cpp的映射

至此,我们可以说cpp已经建立起了和Java的联系了,后续Java可以通过定义的native方法调用到cpp中映射的方法了。但是我们难免还有些疑惑或者好奇,就是是在哪里建立起了这种映射关系?答案就是JNI的注册接口RegisterNatives,其实这是JNI规范的重要接口,理论上它的实现因虚拟机而异,开发者不需要关系,我们只需要知道当我们调用了注册接口之后,cpp的函数就和Java对应的方法建立起了映射关系,调用Java native方法就会准确的调用到cpp的对应函数。

但是毕竟来都来了,再看看ART中关于JNI的部分实现也不是太复杂,不需要什么铺垫。

scss 复制代码
/************************jni.h *********************************************/

 // 这是声明在jni头文件里的注册函数
 jint  (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);




/*************************** jni_internal.cc ***********************************/
// 我们找到注册函数对应的定义实现
 static jint RegisterNatives(JNIEnv* env,
                              jclass java_class,
                              const JNINativeMethod* methods,
                              jint method_count) {
   ...
   ...
   // class_linker 是art中用于加载 链接,解析Java字节码的类
   // 我们常说类的加载过程有三步:加载,链接,初始化,ClassLinker就是负责链接的部分
   // classlinker的主要工作是验证、准备和解析
    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    ...
    ...
    
    for (jint i = 0; i < method_count; ++i) {
      const char* name = methods[i].name;
      const char* sig = methods[i].signature;
      const void* fnPtr = methods[i].fnPtr;
      if (UNLIKELY(name == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i);
        return JNI_ERR;
      } else if (UNLIKELY(sig == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i);
        return JNI_ERR;
      } else if (UNLIKELY(fnPtr == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i);
        return JNI_ERR;
      }
     
     // ArtMethod是Java 方法在虚拟机中运行时的表示
      ArtMethod* m = nullptr;
      bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled();
      for (ObjPtr<mirror::Class> current_class = c.Get();
           current_class != nullptr;
           current_class = current_class->GetSuperClass()) {
        // 通过class methodName signature三个参数来定位Java的方法
        // <true>表示只在标识为native的方法中搜索
        m = FindMethod<true>(current_class, name, sig);
        // 找到就退出循环
        if (m != nullptr) {
          break;
        }

        // false 表示在非native方法中寻找
        m = FindMethod<false>(current_class, name, sig);
        if (m != nullptr) {
          break;
        }
      }
    // 如果m==null表示没找到Java层中定义的本地方法,返回错误
      if (m == nullptr) {
        ...
        ...
        return JNI_ERR;
      } else if (!m->IsNative()) { // 找到了但是不是native标识的,也返回错误
        ...
        ...
        return JNI_ERR;
      }
//走到这里来,说明找到了Java层对应的native方法了
    ...
    ...
    //调用class_linker.RegisterNative 把Java对应的方法m和cpp定义的函数fnPtr建立对应关系
      const void* final_function_ptr = class_linker->RegisterNative(soa.Self(), m, fnPtr);
      
      UNUSED(final_function_ptr);
    }
    return JNI_OK;
  }
  
  /**************************classlinker.cc **************************/
  const void* ClassLinker::RegisterNative(
    Thread* self, ArtMethod* method, const void* native_method) {
...
...
  if (method->IsCriticalNative()) {
    MutexLock lock(self, critical_native_code_with_clinit_check_lock_);
    // Remove old registered method if any.
    auto it = critical_native_code_with_clinit_check_.find(method);
    if (it != critical_native_code_with_clinit_check_.end()) {
      critical_native_code_with_clinit_check_.erase(it);
    }
    // To ensure correct memory visibility, we need the class to be visibly
    // initialized before we can set the JNI entrypoint.
    if (method->GetDeclaringClass()->IsVisiblyInitialized()) {
      method->SetEntryPointFromJni(new_native_method);
    } else {
      critical_native_code_with_clinit_check_.emplace(method, new_native_method);
    }
  } else {
    method->SetEntryPointFromJni(new_native_method); // 设置native函数调用点
  }
  return new_native_method;
}
  
  
  /****************************** art_method.h *************************************/
  // 这是ArtMethod内部的一个属性 
    // Must be the last fields in the method.
  struct PtrSizedFields {
    // Depending on the method type, the data is
    //   - native method: pointer to the JNI function registered to this method
    //                    or a function to resolve the JNI function,
    //   - resolution method: pointer to a function to resolve the method and
    //                        the JNI function for @CriticalNative.
    //   - conflict method: ImtConflictTable,
    //   - abstract/interface method: the single-implementation if any,
    //   - proxy method: the original interface method or constructor,
    //   - default conflict method: null
    //   - other methods: during AOT the code item offset, at runtime a pointer
    //                    to the code item.
    void* data_;  //假如是native方法,data_会指向映射的cpp层定义的函数指针

    // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
    // the interpreter.
    // Java方法的入口
    void* entry_point_from_quick_compiled_code_; // 
  } ptr_sized_fields_;

根据上面的分析我们可知,Java虚拟机加载了字节码之后,就可以在运行时轻易的找到对应的类和方法,然后让它和cpp的函数建立映射关系即可。

Java正常如何调用到cpp

由于Android的application是以Java为主要语言的,所以在cpp层进入Java层之后,逻辑的驱动就以Java为主了。

当我们正常启动一个app时。可能会在某个时刻需要调用native方法,但是我们知道其实app启动完成之前,cpp层已经完全保存好了Java层与cpp层的系统层面必要的方法映射关系了。但是开发者也会有一些自定义的native方法需要建立映射,而这个过程只能是在app启动之后进行了。

由于cpp层在开机时以及应用进程创建时就做好了一切准备工作,所以从Java层到cpp层的调用过程就没有那么复杂了:

  • 提前加载我们打包好的so库
  • 调用自定义的native方法

加载库的逻辑

从第一步起,代码如下

java 复制代码
public class Some{

    static{
        // 加载我们打包的lib.so库
         System.loadLibrary("lib")
        
    }
}



/******************* NativeFun.java *****************************/


public class NativeFun {

    native String  callFromJavaAdd(int x,int y);

    native String  callFromJavaStr(String content,int v);


    public void callFromCpp(String content){
        
    }

}

而loadLibrary方法经过如下调用

scss 复制代码
System.loadLibrary(String libname) 
---Runtime.loadLibrary0(libName,classLoader); 
------Runtime.nativeLoad(name,loader,ldLibraryPath);     

// 最终调用到一个native方法中
Runtime{
        ...
        ...
        // 这是一个系统定义的native 方法,我们直到开机时系统已经把这个方法在JNI中注册过了
        private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

}

这个也是系统提供的JNI方法,实现在核心库libcore.so中,而且在我们使用时已经注册好了,

scss 复制代码
/*****************************Runtime.c*****************************************/

//Runtime.nativeLoad方法所映射的cpp函数 Runtime_nativeLoad
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

// 注册逻辑
static JNINativeMethod gMethods[] = {
  FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
  NATIVE_METHOD(Runtime, nativeGc, "()V"),
  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
  // NATIVE_METHOD是一个宏定义,最终结果是
  //{"nativeLoad","(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)",Runtime_native}
  // 一个JNINativeMethod类型的元素
  NATIVE_METHOD(Runtime, nativeLoad,
                "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)"
                    "Ljava/lang/String;"),
};
// 注册在libcore.so的JNI_OnLoad方法中
void register_java_lang_Runtime(JNIEnv* env) {
  jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}




/****************************************** OpenjdkJvm.cc ************************************/

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == nullptr) {
    return nullptr;
  }

  std::string error_msg;
  {
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
    // 加载本地库
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &error_msg);
    if (success) {
      return nullptr;
    }
  }
...
  return env->NewStringUTF(error_msg.c_str());
}

本地库的加载最终调用了虚拟机的LoadNativeLibrary函数。

ini 复制代码
/************************* art/runtime/jni/java_vm_ext.cc ****************/
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg) {
    ...
    ...

    const char* path_str = path.empty() ? nullptr : path.c_str();
    bool needs_native_bridge = false;
    char* nativeloader_error_msg = nullptr;
    //最终调用dlopen系统调用,打开给定的库
    void* handle = android::OpenNativeLibrary(
            env,
            runtime_->GetTargetSdkVersion(),
            path_str,
            class_loader,
            (caller_location.empty() ? nullptr : caller_location.c_str()),
            library_path.get(),
            &needs_native_bridge,
            &nativeloader_error_msg);

    ...
    ...
    bool was_successful = false;
    // 最终调用dlsym系统调用,找到该so中的JNI_OnLoad方法
    void* sym = library->FindSymbol("JNI_OnLoad", nullptr, android::kJNICallTypeRegular);
    if (sym == nullptr) {
        VLOG(jni) << "[No JNI_OnLoad found in "" << path << ""]";
        was_successful = true;
    } else {
        ...
        ...
        using JNI_OnLoadFn = int(*)(JavaVM*, void*);
        JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
        // 调用JNI_OnLoad()函数
        int version = (*jni_on_load)(this, nullptr);
        ...
        ...
    }

    library->SetResult(was_successful);
    return was_successful;
}

这里有必要解释下dlopen和dlsym这个系统调用:

  • dlopen: 以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程
  • dlsym: 通过dlopen返回的句柄和连接符名称获取函数名或者变量名 此外还有其他对应的系统调用:
  • dlerror:返回出现的错误
  • dlclose: 用来卸载打开的库

OpenNativeLibrary通过调用dlopen打开我们传入的so库。紧接着我们通过FindSymbol间接调用了dlsym,找到so中的JNI_OnLoad函数。而我们的动态注册一般就放在这个函数中。

不过我们还有静态注册的方式来建立映射关系,在so的加载阶段并没有做什么处理,正如之前文章所说,静态注册依赖JNI规定的固定格式的函数名来定位函数,因此只需要在Java层调用native函数时,动态查找对应的函数即可。

Java调用native函数

我们还是从开始的demo中说起,loadlibrary之后,我们可以正常的在合适的地方调用我们的native函数。当我们调用了native函数之后会发生什么呢?

在Java调用函数之前,虚拟机加载class时,会链接它,从中解析出对应的native函数(也是ArtMethod对象),然后为这个native函数设置好JNI调用入口:

scss 复制代码
/******************************* class_linker.cc ************************************/
static void LinkCode(ClassLinker* class_linker,
                     ArtMethod* method,
                     const OatFile::OatClass* oat_class,
                     uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) {
...
...

  if (method->IsNative()) {
    // Set up the dlsym lookup stub. Do not go through `UnregisterNative()`
    // as the extra processing for @CriticalNative is not needed yet.
    // 我们自定义的JNI函数一般IsCriticalNative()是false
    // GetJniDlsymLookupStub()内联到了 art_jni_dlsym_lookup_stub
    method->SetEntryPointFromJni(
        method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub());
       
  }
}


/*************************** runtime_asm_entrypoints.h ***************************/

// art_jni_dlsym_lookup_stub指令链接在汇编代码中,
extern "C" void* art_jni_dlsym_lookup_stub(JNIEnv*, jobject);
static inline const void* GetJniDlsymLookupStub() {
  return reinterpret_cast<const void*>(art_jni_dlsym_lookup_stub);
}


/******************************* jni_entrypoints_arm64.S*************************************/

// 汇编代码调用artFindNativeMethod
    /*
     * Jni dlsym lookup stub.
     */
    .extern artFindNativeMethod
    .extern artFindNativeMethodRunnable
ENTRY art_jni_dlsym_lookup_stub
    // spill regs.
    SAVE_ALL_ARGS_INCREASE_FRAME 2 * 8
    stp   x29, x30, [sp, ALL_ARGS_SIZE]
    .cfi_rel_offset x29, ALL_ARGS_SIZE
    .cfi_rel_offset x30, ALL_ARGS_SIZE + 8
    add   x29, sp, ALL_ARGS_SIZE

    mov x0, xSELF   // pass Thread::Current()
    // Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
    // for @FastNative or @CriticalNative.
    ldr   xIP0, [x0, #THREAD_TOP_QUICK_FRAME_OFFSET]      // uintptr_t tagged_quick_frame
    bic   xIP0, xIP0, #TAGGED_JNI_SP_MASK                 // ArtMethod** sp
    ldr   xIP0, [xIP0]                                    // ArtMethod* method
    ldr   xIP0, [xIP0, #ART_METHOD_ACCESS_FLAGS_OFFSET]   // uint32_t access_flags
    mov   xIP1, #(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
    tst   xIP0, xIP1
    b.ne  .Llookup_stub_fast_or_critical_native
    bl    artFindNativeMethod
    b     .Llookup_stub_continue
    .Llookup_stub_fast_or_critical_native:
    bl    artFindNativeMethodRunnable

类加载阶段对方法进行了解析,给native方法指定了一个调用函数art_jni_dlsym_lookup_stub,而这个函数会链接到一段汇编代码 ,再从汇编代码跳转到artFindNativeMethod方法,去寻找它对应的cpp函数

scss 复制代码
/*****************************jni_entrypoints.cc************************/



extern "C" const void* artFindNativeMethod(Thread* self) {
  DCHECK_EQ(self, Thread::Current());
  Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
  ScopedObjectAccess soa(self);
  return artFindNativeMethodRunnable(self);
}

extern "C" const void* artFindNativeMethodRunnable(Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  Locks::mutator_lock_->AssertSharedHeld(self);  // We come here as Runnable.
  uint32_t dex_pc;
  ArtMethod* method = self->GetCurrentMethod(&dex_pc); // 找到当前正在调用的方法
  DCHECK(method != nullptr);
  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();


  // Check whether we already have a registered native code.
  // 先检查method中是否有我们动态注册的JNI函数,如果是静态注册,将找不到
  const void* native_code = class_linker->GetRegisteredNative(self, method);
  if (native_code != nullptr) {
    return native_code;
  }

  // Lookup symbol address for method, on failure we'll return null with an exception set,
  // otherwise we return the address of the method we found.
  JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm();
  std::string error_msg;
  // 尝试查找静态注册的函数
  native_code = vm->FindCodeForNativeMethod(method, &error_msg, /*can_suspend=*/ true);
  if (native_code == nullptr) {
    LOG(ERROR) << error_msg;
    self->ThrowNewException("Ljava/lang/UnsatisfiedLinkError;", error_msg.c_str());
    return nullptr;
  }

  // 注册当前JNI函数,并返回函数指针
  return class_linker->RegisterNative(self, method, native_code);
}



/********************* java_vm_ext.cc *********************************/



void* JavaVMExt::FindCodeForNativeMethod(ArtMethod* m, std::string* error_msg, bool can_suspend) {
  CHECK(m->IsNative());
  ObjPtr<mirror::Class> c = m->GetDeclaringClass();
  // If this is a static method, it could be called before the class has been initialized.
  CHECK(c->IsInitializing() || !m->NeedsClinitCheckBeforeCall())
      << c->GetStatus() << " " << m->PrettyMethod();
  Thread* const self = Thread::Current();
  // 从本地已加载库的库中去查找
  void* native_method = libraries_->FindNativeMethod(self, m, error_msg, can_suspend);
  if (native_method == nullptr && can_suspend) {
    // Lookup JNI native methods from native TI Agent libraries. See runtime/ti/agent.h for more
    // information. Agent libraries are searched for native methods after all jni libraries.
    native_method = FindCodeForNativeMethodInAgents(m);
  }
  return native_method;
}


void* FindNativeMethod(Thread* self, ArtMethod* m, std::string* detail, bool can_suspend)
      REQUIRES(!Locks::jni_libraries_lock_)
      REQUIRES_SHARED(Locks::mutator_lock_) {
      // JniShortName是指静态注册是不包含参数类型的函数名
    std::string jni_short_name(m->JniShortName());
    // // JniLongName是指静态注册包含参数类型的函数名
    std::string jni_long_name(m->JniLongName());
    ...
    ...
    void* native_code = nullptr;
    android::JNICallType jni_call_type =
        m->IsCriticalNative() ? android::kJNICallTypeCriticalNative : android::kJNICallTypeRegular;
    if (can_suspend) { // can_suspend传入参数为true
      ScopedThreadSuspension sts(self, ThreadState::kNative);
      //查找对应的JNI方法
      native_code = FindNativeMethodInternal(self,
                                             declaring_class_loader_allocator,
                                             shorty,
                                             jni_short_name,
                                             jni_long_name,
                                             jni_call_type);
    } 
    ...
    ...
    // 返回JNI函数
    if (native_code != nullptr) {
      return native_code;
    }
    ...
    ...
  }
  //具体查找方式
  void* FindNativeMethodInternal(Thread* self,
                                 void* declaring_class_loader_allocator,
                                 const char* shorty,
                                 const std::string& jni_short_name,
                                 const std::string& jni_long_name,
                                 android::JNICallType jni_call_type)
      REQUIRES(!Locks::jni_libraries_lock_) {
    // 遍历当前缓存的library
    for (const auto& lib : libraries_) {
      SharedLibrary* const library = lib.second;
      const char* arg_shorty = library->NeedsNativeBridge() ? shorty : nullptr;
      //尝试用short_name从当前遍历的库中去查找函数
      void* fn = library->FindSymbol(jni_short_name, arg_shorty, jni_call_type);
      if (fn == nullptr) {
      //如果找不到的话,再尝试用long_name从当前遍历的库中去查找函数
        fn = library->FindSymbol(jni_long_name, arg_shorty, jni_call_type);
      }
      if (fn != nullptr) {
        //最终返回对应的函数
        return fn;
      }
    }
    return nullptr;
  }
  


// 以下是构建JNI的静态函数的方式  
  /**************************** art_method.cc***************************************/
std::string ArtMethod::JniShortName() {
  return GetJniShortName(GetDeclaringClassDescriptor(), GetName());
}

std::string ArtMethod::JniLongName() {
  std::string long_name;
  long_name += JniShortName();
  long_name += "__";

  std::string signature(GetSignature().ToString());
  signature.erase(0, 1);
  signature.erase(signature.begin() + signature.find(')'), signature.end());

  long_name += MangleForJni(signature);

  return long_name;
}


/*******************************descriptors_names.cc******************************************/

std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) {
  // Remove the leading 'L' and trailing ';'...
  std::string class_name(class_descriptor);
  CHECK_EQ(class_name[0], 'L') << class_name;
  CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name;
  class_name.erase(0, 1);
  class_name.erase(class_name.size() - 1, 1);

  std::string short_name;
  short_name += "Java_";
  short_name += MangleForJni(class_name);
  short_name += "_";
  short_name += MangleForJni(method);
  return short_name;
}
 

至此关于Java调用JNI函数的全过程基本完成了,简单总结一下,就是在Java类加载阶段,虚拟机就会解析类的结构,同时为native函数指定调用的函数,当方法被调用时,会跳转到指定的函数,然后再对应的函数里找是否有已注册的已注册的JNI函数,假如没找到就用固定命名方式利用dlsym系统调用来查找函数。

cpp正常如何调用到Java

当我们理解了从Java如何调用到cpp的过程后,从cpp调用Java的过程就显得不是很复杂了。因为本身Java就运行在虚拟机的cpp代码之上,从cpp层是很容易找到运行时的Java方法的。

ini 复制代码
// 假设我们定义了一个从cpp调用Java的方法,


extern "C"
JNIEXPORT void JNICALL call_java_method(JNIEnv *env,jobject obj) {
    const char *className = "com/example/applicationnative/NativeFun";
    jclass  clazz = env->FindClass(className);
    if (clazz == nullptr){
        return;
    }
    jmethodID method_id = env->GetMethodID(clazz,"callFromCpp","(Ljava/lang/String;)V");
    jstring content = env->NewStringUTF("来自cpp的问候");
    env->CallVoidMethod(obj,method_id,content);
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(content);
}

你会发现,其实我们大概需要关注三个函数的实现即可,分别是:

  • FindClass 查找类
  • GetMethodID 查找方法
  • CallVoidMethod 调用方法

findClass

cpp 复制代码
ObjPtr<mirror::Class> ClassLinker::FindClass(Thread* self,
                                             const char* descriptor,
                                             Handle<mirror::ClassLoader> class_loader) {
    ...
    ...
    // 从已经加载的类中寻找对应的类
    ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());
    if (klass != nullptr) {
        return EnsureResolved(self, descriptor, klass);
    }
    // 如果没找到,类又不是数组类I型那个,class_loader是null,就从boot class loader中去寻找
    if (descriptor[0] != '[' && class_loader == nullptr) {
        // Non-array class and the boot class loader, search the boot class path.
        ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
        if (pair.second != nullptr) {
            return DefineClass(self,
                               descriptor,
                               hash,
                               ScopedNullHandle<mirror::ClassLoader>(),
                               *pair.first,
                               *pair.second);
        }
        ...
    }
    ObjPtr<mirror::Class> result_ptr;
    bool descriptor_equals;
    if (descriptor[0] == '[') { //如果是数组类型
        result_ptr = CreateArrayClass(self, descriptor, hash, class_loader);
        ...
        ..
    } else {
        // 从BaseDexClassLoader中寻找
        bool known_hierarchy =
                FindClassInBaseDexClassLoader(self, descriptor, hash, class_loader, &result_ptr);
        ...
        ...
    }
    ...
    ...

    return result_ptr;
}

ObjPtr<mirror::Class> ClassLinker::LookupClass(Thread* self,
                                               const char* descriptor,
                                               size_t hash,
                                               ObjPtr<mirror::ClassLoader> class_loader) {
    ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
    // 获取class_loader对应的class_table
    ClassTable* const class_table = ClassTableForClassLoader(class_loader);
    if (class_table != nullptr) {
        // 从table中查找对应的类
        ObjPtr<mirror::Class> result = class_table->Lookup(descriptor, hash);
        if (result != nullptr) {
            return result;
        }
    }
    return nullptr;
}

GetMethodID

rust 复制代码
static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {
     ...
     ...
    return FindMethodID<kEnableIndexIds>(soa, java_class, name, sig, false);
}

// 模板函数
template<bool kEnableIndexIds>
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,
                              const char* name, const char* sig, bool is_static)
REQUIRES_SHARED(Locks::mutator_lock_) {
// 调用FindMethodJNI函数
        return jni::EncodeArtMethod<kEnableIndexIds>(FindMethodJNI(soa, jni_class, name, sig, is_static));
}



ArtMethod* FindMethodJNI(const ScopedObjectAccess& soa,
                         jclass jni_class,
                         const char* name,
                         const char* sig,
                         bool is_static) {
    // 获取初始化之后的类对象
    ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));
    if (c == nullptr) {
        return nullptr;
    }
    ArtMethod* method = nullptr;
    auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
    if (c->IsInterface()) {
        method = c->FindInterfaceMethod(name, sig, pointer_size);
    } else {
        // 我们调用的类一般不是接口,调用FindClassMethod
        method = c->FindClassMethod(name, sig, pointer_size);
    }
    ...
    ...
    return method;
}


// FindClassMethod最终调用到这个模板函数
template <typename SignatureType>
static inline ArtMethod* FindClassMethodWithSignature(ObjPtr<Class> this_klass,
                                                      std::string_view name,
                                                      const SignatureType& signature,
                                                      PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_) {
        // Search declared methods first.
        //从类中逐个扫描
        for (ArtMethod& method : this_klass->GetDeclaredMethodsSlice(pointer_size)) {
            ArtMethod* np_method = method.GetInterfaceMethodIfProxy(pointer_size);
            // 对比函数名,函数签名,两者一致就返回
            if (np_method->GetNameView() == name && np_method->GetSignature() == signature) {
            
                return &method;
            }
        }

        ...
        ...
        return uninherited_method;  // Return the `uninherited_method` if any.
}

CallVoidMethod

当可以找到Java运行时的方法所对应的ArtMethod对象的ID之后,就是调用方法了。

scss 复制代码
/************************** art/runtime/reflection.cc ***************************/
template <>
JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
                                           jobject obj,
                                           jmethodID mid,
                                           va_list args) {
    // 把methodid解码为ArtMethod指针 调用InvokeVirtualOrInterfaceWithVarArgs
    return InvokeVirtualOrInterfaceWithVarArgs(soa, obj, jni::DecodeArtMethod(mid), args);
}

template <>
JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
                                           jobject obj,
                                           ArtMethod* interface_method,
                                           va_list args) {
    ...
    ...
    uint32_t shorty_len = 0;
    const char* shorty =
            method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len);
    JValue result;
    ArgArray arg_array(shorty, shorty_len);
    arg_array.BuildArgArrayFromVarArgs(soa, receiver, args);
    InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
    if (is_string_init) {
        // For string init, remap original receiver to StringFactory result.
        UpdateReference(soa.Self(), obj, result.GetL());
    }
    return result;
}

void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa,
                        ArtMethod* method, ArgArray* arg_array, JValue* result,
                        const char* shorty)
REQUIRES_SHARED(Locks::mutator_lock_) {
        uint32_t* args = arg_array->GetArray();

        // 调用Java方法
        method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
}

总结

关于JNI的实现,我们大概按照三个阶段来分析的,启动阶段的JNI构建阶段,正常运行时Java_call_JNI的阶段,以及在jni_call_java的阶段

构建阶段,在系统启动过程中,系统分加载jni相关的核心库,并导入相关的函数实现,同时创建虚拟机对象,并启动虚拟机,注册与此相关的JNI函数。

运行时java_call_jni app进程初始化时,系统会加载类时会对native方法进行解析,把native方法都指向一个固定的函数,然后链接到一段汇编代码中 Java层调用native方法前则需要先加载so库,系统会调用JNI_OnLoad函数,一般我们会在这个回调中添加注册JNI函数的方法,因此JNI_OnLoad也可能会触发注册函数相关的逻辑。在调用native方法后,我们会根据class链接时指向的调用路径,最终来到一段固定的汇编代码,通过artFindNativeMethod找到并执行注册的JNI函数。

JNI_call_java 从JNI call Java时,一般一切环境都已经准备好了,虚拟机通过classname找到jclass,再利用函数信息找到运行时Java方法所对应的ArtMethod的id,接着通过这些信息调用函数即可。

相关推荐
怀旧6661 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索3 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨8 分钟前
Filter和Listener
java·filter
qq_49244844613 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国21 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
web Rookie24 分钟前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust32 分钟前
css:基础
前端·css
帅帅哥的兜兜32 分钟前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
yi碗汤园35 分钟前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称36 分钟前
购物车-多元素组合动画css
前端·css