从源码角度看JNI

编程入门 行业动态 更新时间:2024-10-25 18:33:40

从源码<a href=https://www.elefans.com/category/jswz/34/1768357.html style=角度看JNI"/>

从源码角度看JNI

简介

Android NDK开发中,常常因为效率、安全等原因,需要借助JNI的机制调用到Native环境中进行c/cpp操作,常见的Java层需要调用Native层的代码时的标准流程是这样的:

  1. 调用loadLibrary,依靠dlxxx系列方法加载动态链接库,然后调用库中的JNI_Onload方法,解析并保存头文件中的符号表
  2. JAVA层调用事先声明的native方法
  3. 虚拟机通过预先加载的符号表调用Native层代码,同时会实例化一个JNIEnv指针,Native层可以借用它调用JAVA层的代码

这篇文章里,我将会提供典型的JAVA与Native使用JNI接口实现双向调用的实例,同时也会从源码的角度分析两种JNI动态注册的原理

架构图

  • Android应用进程各自运行在专属的虚拟机中
  • 通过JNIEnv,进程可以在Native环境中使用JNI接口对JAVA层进行方法调用等操作
  • JNI技术是JAVA与Native之间相互调用的"桥梁"
  • 系统SysCall机制,Native层可以调用到Kernel层

示例

下面的示例中分别会给出从JAVA调用到Native、从Native调用到JAVA的两个典型示例

在JAVA中调用Native方法

  • 从JAVA调用到Native的第一步是需要创建JAVA project,并进行native方法声明与调用
  • public class HelloJni extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_hello_jni);TextView tv = (TextView)findViewById(R.id.hello_textview);// 通过JNI调用native方法tv.setText(stringFromJNI());}// 声明native方法public native String stringFromJNI();// 初始化动态so库static {System.loadLibrary("hello-jni");}
    }

     

在cpp中实现native方法Java_com_example_hellojni_HelloJni_stringFromJNI

#include <jni.h>
#include <string>
#include "format.h"extern "C" {JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv * env, jobject obj )
{std::string hello = fmt::format("Hello from C++ Format! GCC version: {}.{}", __GNUC__, __GNUC_MINOR__);return env->NewStringUTF(hello.c_str());
}}

和使用RegisterMethodsOrDie方法进行注册不同,这个示例使用了JNIEXPORTJNICALL的命名规则进行native方法的初始化,通过这种特殊的规则,虚拟机可以在不主动调用注册方法的前提下进行静态注册

  1. 使用makefile编译动态so库
  2. LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)$(call import-add-path,$(LOCAL_PATH))LOCAL_MODULE    := hello-jni
    LOCAL_SRC_FILES := hello-jni.cpp
    LOCAL_WHOLE_STATIC_LIBRARIES := cppformat_staticinclude $(BUILD_SHARED_LIBRARY)$(call import-module,../../cppformat)

    在Native中调用JAVA方法声明一个类及方法

  1. 声明一个类及方法
  2. public class JniHandle {public String getStringForJava() {return "string from method in java";}
    }

     

在Native中使用JNI调用getStringForJavaJAVA方法

JNIEXPORT void JNICALL
Java_com_example_hellojni_callJavaMethodFromJni(JNIEnv *env, jclass type) {// 通过JNIEnv获取到jclassjclass jniHandle = (*env)->FindClass(env, "com/example/JniHandle");if (NULL == jniHandle) {LOGW("can't find jniHandle");return;}jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "<init>", "()V");if (NULL == constructor) {LOGW("can't constructor JniHandle");return;}// 创建一个JniHanlde的实例jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor);if (NULL == jniHandleObject) {LOGW("can't new JniHandle");return;}// 通过JNIEnv获取到jmethodjmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava", ()Ljava/lang/String;");if (NULL == getStringForJava) {LOGW("can't find method of getStringForJava");(*env)->DeleteLocalRef(env, jniHandle);(*env)->DeleteLocalRef(env, jniHandleObject);return;}// 调用该JAVA方法jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava);const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);// 释放局部引用(*env)->DeleteLocalRef(env, jniHandle);(*env)->DeleteLocalRef(env, jniHandleObject);(*env)->DeleteLocalRef(env, result);
}

例子中我给出了静态注册的常规写法,下面详细分析下动态注册在Android源码中的两种常用场景:开机JNI初始化与调用System.loadLibrary加载动态库

开机JNI初始化

安卓系统开机过程中,会先启动Init进程,随后再拉起zygote进程。我们知道所有的APP应用进程都是通过fork zygote进程创建的,所以zygote进程在启动的过程中做了虚拟机初始化的操作,这其中就包括了framework所需要的所有JNI接口的注册

frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));...if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);}...
}

在init拉起zygote后,会运行zygote的可执行程序app_main

frameworks/base/core/jni/AndroidRuntime.cpp

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{...if (startReg(env) < 0) {ALOGE("Unable to register all android natives\n");return;}...
}// 声明函数
extern int register_android_os_MessageQueue(JNIEnv* env);static const RegJNIRec gRegJNI[] = {...// 初始化函数指针REG_JNI(register_android_os_MessageQueue),...
}/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{...// 注册framework所需要的JNI接口if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {env->PopLocalFrame(NULL);return -1;}...return 0;
}static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{for (size_t i = 0; i < count; i++) {// 触发对应的注册函数if (array[i].mProc(env) < 0) {return -1;}}return 0;
}

可以看到这个JNI注册的核心其实就是遍历RegJNIRec数组并触发RegJNIRec.mProc的调用

gRegJNI数组声明中频繁的使用到了REG_JNI这个宏,这里特别的看一下它的实现:

#define REG_JNI(name)      { name }
struct RegJNIRec {int (*mProc)(JNIEnv*);
};

REG_JNI的声明使用了填充结构,这里以register_android_os_MessageQueue为例,实际上是让mProc指向了它的函数指针,当调用register_jni_procs时,会触发register_android_os_MessageQueue这个extern函数,当程序执行这个它时,会自动调用到android_os_MessageQueue下的对应函数:

frameworks/base/core/jni/android_os_MessageQueue.cpp

#include "core_jni_helpers.h"static const JNINativeMethod gMessageQueueMethods[] = {/* name, signature, funcPtr */{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },{ "nativeSetFileDescriptorEvents", "(JII)V",(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents}
};int register_android_os_MessageQueue(JNIEnv* env) {// 将JNI方法与对应的android.os.MessageQueue java类中的native方法进行绑定int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods, NELEM(gMessageQueueMethods));...return res;
}

大致来看,各个register_xxx方法其实都是调用到了在core_jni_helpers.h头文件中声明的RegisterMethodsOrDie内联方法:

frameworks/base/core/jni/core_jni_helpers.h

static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");return res;
}

 

更多推荐

从源码角度看JNI

本文发布于:2023-07-28 16:06:31,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1244939.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:角度看   源码   JNI

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!