Lynx内核是由C++编写,方便跨平台使用。这样在Android端与Java层通讯就须要使用JNI,Lynx在JNI层为了不直接手写JNI注册代码以及反射调用Java的代码,使用自动化的方式来自动生成这部分代码。java
一般Java调用C/C++方法的JNI方法注册分为静态注册和动态注册两种。android
将Java中的Native方法在C/C++文件声明对应成Java_$PackageName_$MethodName(JNIEnv *env, args…) 完成完成静态注册。以Lynx代码中的自动化测试模块代码GTestDriver.java为例c++
package com.lynx.gtest;
...
public class GTestDriver {
...
//java native 方法
private static native int nativeRunGTestsNative(String[] gtestCmdLineArgs);
}
复制代码
对于C方法的编写以下git
#include <jni.h>
...
JNIEXPORT jint Java_com_lynx_gtest_nativeRunGTestsNative(JNIEnv *env, jobjectArray gtestCmdLineArgs)
...
复制代码
从例子中能够看出静态注册的名字很是长,不便于书写。而且在初次调用的时候须要依据名字找到对应方法,对于大型工程中方法数多的状况下,效率低且易出错。github
经过在C/C++中声明一个JNINativeMethod nativeMethod[]数组,而后在JNI_OnLoad中调用RegisterNatives方法来完成动态注册。譬如上面的例子用动态注册的方式为:数组
#include <jni.h>
...
static jint RunGTestsNative(JNIEnv *env, jclass jcaller, jobjectArray gtestCmdLineArgs) {
....
}
static const JNINativeMethod kMethodsGTestDriver[] = {
{ "nativeRunGTestsNative",
"("
"[Ljava/lang/String;"
")"
"I", reinterpret_cast<void*>(RunGTestsNative) },
};
...
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv *env;
if ((*jvm) -> GetEnv(jvm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
jclass clz = (*env) -> FindClass(env, "com/lynx/gtest/GTestDriver");
(*env) -> RegisterNatives(env, clz, kMethodsGTestDriver, sizeof(kMethodsGTestDriver) / sizeof(kMethodsGTestDriver[0]));
return JNI_VERSION_1_6;
}
复制代码
动态注册是Lynx JNI的基础,后面展开介绍。框架
JNI的C代码调用Java代码。实现原理:使用JNI提供的反射接口来反射获得Java方法,进行调用。以Lynx代码中的文本测量的代码LabelMeasurer.java为例jvm
package com.lynx.core;
...
public class LabelMeasurer {
...
@CalledByNative
public static Size measureLabelSize(String text, Style style, int width, int widthMode, int height, int heightMode) {
...
}
}
复制代码
在C/C++代码中调用measureLabelSize须要经过反射来完成,基本实现以下函数
// 查找LabelMeasurer类
jclass clazz = (*env)->FindClass(env,"com/lynx/core/LabelMeasurer");
//获取measureLabelSize方法
jmethodID method = (*env)->GetMethodID(env,clazz, "measureLabelSize", "(Ljava/lang/String;Lcom/lynx/base/Style;IIII)Lcom/lynx/base/Size;");
//执行
jobject ret = env->CallStaticObjectMethod(clazz, method, text, style,
width, widthMode, height, heightMode);
复制代码
编写这样的调用会有很是多的重复代码,当接口须要进行修改时很是不方便。性能
从上述介绍可得,JNI的注册是一个重复性的操做。Lynx为了提升效率,将JNI的注册交由自动化脚本生成。
Lynx上对Native注册进行了约定,全部对Java注册的Native方法都以native开头,自动化脚本会在Java文件中找到这些方法,并自动生成相应的文件。
一样以 GTestDriver.java 为例,能够看到GTestDriver中包含JNI的Native方法nativeRunGTestsNative,而且以约定的native关键开头。对于这个文件会由prebuild.sh脚本执行生成一个GTestDriver_jni.h的头文件
#ifndef com_lynx_gtest_GTestDriver_JNI
#define com_lynx_gtest_GTestDriver_JNI
#include <jni.h>
#include "base/android/android_jni.h"
// Step 1: forward declarations.
namespace {
const char kGTestDriverClassPath[] = "com/lynx/gtest/GTestDriver";
// Leaking this jclass as we cannot use LazyInstance from some threads.
jclass g_GTestDriver_clazz = NULL;
#define GTestDriver_clazz(env) g_GTestDriver_clazz
} // namespace
static jint RunGTestsNative(JNIEnv* env, jclass jcaller, jobjectArray gtestCmdLineArgs);
// Step 2: method stubs.
// Step 3: RegisterNatives.
static const JNINativeMethod kMethodsGTestDriver[] = {
{ "nativeRunGTestsNative",
"("
"[Ljava/lang/String;"
")"
"I", reinterpret_cast<void*>(RunGTestsNative) },
};
static bool RegisterNativesImpl(JNIEnv* env) {
g_GTestDriver_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
base::android::GetClass(env, kGTestDriverClassPath).Get()));
const int kMethodsGTestDriverSize =
sizeof(kMethodsGTestDriver)/sizeof(kMethodsGTestDriver[0]);
if (env->RegisterNatives(GTestDriver_clazz(env),
kMethodsGTestDriver,
kMethodsGTestDriverSize) < 0) {
return false;
}
return true;
}
#endif // com_lynx_gtest_GTestDriver_JNI
复制代码
Lynx的JNI使用的动态注册方式,将Java文件中定义的nativeRunGTestsNative与RunGTestsNative函数指针关联,这样对头文件中定义的RunGTestsNative方法进行实现,并在jni_onload的时候调用RegisterNativesImpl就能够实现对JNI方法的完整注册。这个方式比起JNI原有的方式也简单不少,隐藏了不少重复性质的代码。
Lynx对C/C++调用Java方法也作了定义,若是在Java文件中定义了能够被C/C++调用的代码,能够在方法前加上@CalledByNative,自动化脚步会在java文件中找到这些方法,并自动生成相应的文件。一样以上文中的 LabelMeasurer.java 为例。
在Java文件中申明了一个能够被C/C++使用的函数measureLabelSize。进过prebuild.sh脚本处理以后会生成LabelMeasurer_jni.h
#ifndef com_lynx_core_LabelMeasurer_JNI
#define com_lynx_core_LabelMeasurer_JNI
#include <jni.h>
#include "base/android/android_jni.h"
// Step 1: forward declarations.
namespace {
const char kLabelMeasurerClassPath[] = "com/lynx/core/LabelMeasurer";
// Leaking this jclass as we cannot use LazyInstance from some threads.
jclass g_LabelMeasurer_clazz = NULL;
#define LabelMeasurer_clazz(env) g_LabelMeasurer_clazz
} // namespace
// Step 2: method stubs.
static intptr_t g_LabelMeasurer_measureLabelSize = 0;
static base::android::ScopedLocalJavaRef<jobject>
Java_LabelMeasurer_measureLabelSize(JNIEnv* env, jstring text,
jobject style,
int width,
int widthMode,
int height,
int heightMode) {
jmethodID method_id =
base::android::GetMethod(
env, LabelMeasurer_clazz(env),
base::android::STATIC_METHOD,
"measureLabelSize",
"("
"Ljava/lang/String;"
"Lcom/lynx/base/Style;"
"I"
"I"
"I"
"I"
")"
"Lcom/lynx/base/Size;",
&g_LabelMeasurer_measureLabelSize);
jobject ret =
env->CallStaticObjectMethod(LabelMeasurer_clazz(env),
method_id, text, style, int(width), int(widthMode), int(height),
int(heightMode));
base::android::CheckException(env);
return base::android::ScopedLocalJavaRef<jobject>(env, ret);
}
// Step 3: RegisterNatives.
static bool RegisterNativesImpl(JNIEnv* env) {
g_LabelMeasurer_clazz = reinterpret_cast<jclass>(env->NewGlobalRef(
base::android::GetClass(env, kLabelMeasurerClassPath).Get()));
return true;
}
#endif // com_lynx_core_LabelMeasurer_JNI
复制代码
这样在使用的时候就能够直接引入头文件,并调用Java_LabelMeasurer_measureLabelSize方法便可。使用起来也是很是简便的。
自动生成的文件省去了编写反射获取函数方法以及调用的处理,所有有脚本直接生成,对于获取method id的方式,Lynx作了一层封装,能够对method id进行保存,这样查找只会在第一次调用的时候执行,节约总体调用时间 , 具体能够base/android/android_jni.h 中查看。
同时Lynx也对jobject进行了自动引用的处理,在再也不使用jobject对象的时候会自动进行DeleteLocalRef,避免忘记释放后local ref过多超出最大值的状况,具体能够在 base/android/scoped_java_ref.h 中查看。
git clone https://github.com/hxxft/lynx-native.git
抽取所需文件并将文件添加入CMakeLists.txt
base/android/android_jni.h
base/android/android_jni.cc
base/android/java_type.h
base/android/java_type.cc
base/android/scoped_java_ref.h
base/android/scoped_java_ref.cc
抽取build文件夹,将jni_load.cc加入CMakeLists.txt,并根据需求修改此文件
修改jni_files加入须要自动生成JNI代码的Java文件
编写Java文件,使用前面讲解时使用的方法注释函数或者修饰方法
修改prebuild.sh中ROOT_LYNX_JAVA_PATH等路径,根据本身工程配置进行修改
在编译以前执行prebuild.sh生成所须要的文件
这篇文章主要介绍Lynx的JNI自动生成的方法,自动生成的方法省去了大部分重复代码,所以在编写代码过程当中能够专一于方法的实现上,对须要使用JNI的工程来讲,能够提供巨大的便利。
请持续关注 Lynx,一个高性能跨平台开发框架。