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,一個高性能跨平臺開發框架。