JNI方法注册方式分为动态与静态注册。
1.函数静态注册
1.1JNI层函数格式:Java_包名_类名 _方法名
下划线隔开,通过javah生成带签名的函数,然后去实现这些函数,这种也是官方推荐的方式。
Java层方法声明成native方法:
public native String signture(String sig);
JNI层对应的方法:
JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
(JNIEnv *env, jobject jobject1, jstring jstring1) {
//md5加密
const char *jcstr = (env)->GetStringUTFChars(jstring1, 0);
MD5 md5;
md5.update(jcstr);
string jstring2 = md5.toString();
//package_name
jstring packName = getPackname(env, jobject1, jobject1);
const char *c = env->GetStringUTFChars(packName, 0);
LOGE("getPackageName: %s", c);
//signature
jstring signatures = getSignature(env, jobject1, jobject1);
const char *signaturesc = env->GetStringUTFChars(signatures, 0);
LOGE("signatures: %s", signaturesc);
return env->NewStringUTF(jstring2.c_str());
}
1.2 优缺点:
优点:
java与jni方法对应清晰
缺点:
必须按照函数规则,方法名很长
运行时查找函数效率低
2.函数动态注册
2.1 动态注册
定义的native方法
public native String stringFromJNI();
public native String signture(String sig);
int RegisterNatives(JNIEnv *env) {
jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
if (clazz == NULL) {
LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
return JNI_ERR;
}
// 动态注册的函数数组
JNINativeMethod methods_MainActivity[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
{"signture", "(Ljava/lang/String;)V", (void *) signture}
};
LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
// int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
// 实际注册的函数,clazz:对象,methods_MainActivity:函数数组
//sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]): 方法个数
return env->RegisterNatives(clazz, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}
2.2RegisterNatives函数说明:
env->RegisterNatives(clazz, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
RegisterNatives为函数动态注册的方法
参数:
clazz:java对象
methods_MainActivity:函数数组
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]):计算函数个数
2.3JNINativeMethod说明:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
JNINativeMethod为一个结构体。定义在jni.h头文件中。
const char* name:java层native函数名称
const char* signature:函数签名描述
void* fnPtr:Native对应的函数指针
那么动态注册的这个方法RegisterNatives 调用时机是什么时候呢?下来介绍下JNI_OnLoad(JavaVM* vm, void* reserved)函数。那么问题来了,JNI_OnLoad函数什么时候会被调用呢?
原来是当通过System.loadLibrary()加载so的时候,VM会立即调用JNI_OnLoad函数。所以一些初始化的工作可以放到JNI_OnLoad函数里去完成。所以动态注册就在这个函数里完成java native方法与so函数之间的绑定关系。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jint result = RegisterNatives(env);
LOGE("RegisterNatives result: %d", result);
return JNI_VERSION_1_6;
}
动态注册在framework层使用的多,而静态注册一般我们平时开发在上层用的较多,也是官方推荐的方式。
3.函数如何加载
3.1 静态注册的函数映射
用javah风格的代码,则dvm调用dvmResolveNativeMethod进行动态延迟解析,直到需要调用的时候才会解析。
延迟加载
void dvmResolveNativeMethod(const u4* args, JValue* pResult,
const Method* method, Thread* self)
{
ClassObject* clazz = method->clazz;
/*
* If this is a static method, it could be called before the class
* has been initialized.
*/
if (dvmIsStaticMethod(method)) {
if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
assert(dvmCheckException(dvmThreadSelf()));
return;
}
} else {
assert(dvmIsClassInitialized(clazz) ||
dvmIsClassInitializing(clazz));
}
/* start with our internal-native methods */
//在内部本地方法表中查询
DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
if (infunc != NULL) {
/* resolution always gets the same answer, so no race here */
IF_LOGVV() {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
LOGVV("+++ resolved native %s.%s %s, invoking",
clazz->descriptor, method->name, desc);
free(desc);
}
if (dvmIsSynchronizedMethod(method)) {
ALOGE("ERROR: internal-native can't be declared 'synchronized'");
ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);
dvmAbort(); // harsh, but this is VM-internal problem
}
DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
dvmSetNativeFunc((Method*) method, dfunc, NULL);
dfunc(args, pResult, method, self);
return;
}
/* now scan any DLLs we have loaded for JNI signatures */
// 去已加载的动态库中查询
void* func = lookupSharedLibMethod(method);
if (func != NULL) {
/* found it, point it at the JNI bridge and then call it */
dvmUseJNIBridge((Method*) method, func);
(*method->nativeFunc)(args, pResult, method, self);
return;
}
IF_ALOGW() {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
ALOGW("No implementation found for native %s.%s:%s",
clazz->descriptor, method->name, desc);
free(desc);
}
dvmThrowUnsatisfiedLinkError("Native method not found", method);
}
去以加载的动态库中查询,这个时候会调用findMethodInLib函数,这个函数就是动态库中没有重载JNI_OnLoad 函数。然后在动态库中查询注册函数。
/*
* (This is a dvmHashForeach callback.)
*
* Search for a matching method in this shared library.
*
* TODO: we may want to skip libraries for which JNI_OnLoad failed.
*/
static int findMethodInLib(void* vlib, void* vmethod)
{
const SharedLib* pLib = (const SharedLib*) vlib;
const Method* meth = (const Method*) vmethod;
char* preMangleCM = NULL;
char* mangleCM = NULL;
char* mangleSig = NULL;
char* mangleCMSig = NULL;
void* func = NULL;
int len;
if (meth->clazz->classLoader != pLib->classLoader) {
ALOGV("+++ not scanning '%s' for '%s' (wrong CL)",
pLib->pathName, meth->name);
return 0;
} else
ALOGV("+++ scanning '%s' for '%s'", pLib->pathName, meth->name);
/*
* First, we try it without the signature.
*/
preMangleCM =
createJniNameString(meth->clazz->descriptor, meth->name, &len);
if (preMangleCM == NULL)
goto bail;
// java与native进行替换,替换成javah风格函数
mangleCM = mangleString(preMangleCM, len);
if (mangleCM == NULL)
goto bail;
ALOGV("+++ calling dlsym(%s)", mangleCM);
func = dlsym(pLib->handle, mangleCM);
if (func == NULL) {
mangleSig =
createMangledSignature(&meth->prototype);
if (mangleSig == NULL)
goto bail;
mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);
if (mangleCMSig == NULL)
goto bail;
sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);
ALOGV("+++ calling dlsym(%s)", mangleCMSig);
func = dlsym(pLib->handle, mangleCMSig);
if (func != NULL) {
ALOGV("Found '%s' with dlsym", mangleCMSig);
}
} else {
ALOGV("Found '%s' with dlsym", mangleCM);
}
bail:
free(preMangleCM);
free(mangleCM);
free(mangleSig);
free(mangleCMSig);
return (int) func;
}
mangleString(preMangleCM, len): java与native进行替换,替换成javah生成的风格函数。
总结几步:
1.vm调用 dvmResolveNativeMethod函数
2.void* func = lookupSharedLibMethod(method) 去已加载的动态库中查询函数的实现。
3.查询到然后执行下面函数将JNI函数地址保持到ClassObject中
dvmUseJNIBridge((Method) method, func)
(method->nativeFunc)(args, pResult, method, self)
4.最后将函数添加到ClassObject结构体中的directMethods结构体成员中。
3.2 动态注册的函数映射
SO加载及动态注册流程图:
3.2.1 Java层与native层方法是如何注册并映射的
System.loadLibrary
----Runtime.loadLibrary0
------Runtime.doLoad
--------Runtime.nativeLoad
SO加载过程如下:
System.loadLibrary
@CallerSensitive
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
Runtime.loadLibrary0
@CallerSensitive
public void loadLibrary(String libname) {
loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
Runtime.doLoad
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List candidates = new ArrayList();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
Runtime.nativeLoad
private String doLoad(String name, ClassLoader loader) {
String librarySearchPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);
}
}
有过JNI开发的同学应该熟悉System.loadLibrary这个方法,函数的注册映射就是从这里开始的。最后调用nativeLoad这个native方法,对应jni的函数java_lang_Runtime.cpp中Dalvik_java_lang_Runtime_nativeLoad然后调用vm/ Native.cpp中dvmLoadNativeCode函数
// platform/dalvik2/vm/native/java_lang_Runtime.cpp
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult) {
StringObject* fileNameObj = (StringObject*) args[0];
Object* classLoader = (Object*) args[1];
StringObject* ldLibraryPathObj = (StringObject*) args[2];
...
bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
}
//platform/dalvik2/+/refs/heads/master/vm/Native.cpp
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
char** detail)
{
SharedLib* pEntry;
void* handle;
bool verbose;
/* reduce noise by not chattering about system libraries */
verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);
if (verbose)
ALOGD("Trying to load lib %s %p", pathName, classLoader);
*detail = NULL;
/*
* See if we've already loaded it. If we have, and the class loader
* matches, return successfully without doing anything.
*/
// 检查是否已经加载过
pEntry = findSharedLibEntry(pathName);
if (pEntry != NULL) {
//classLoader是否相同,一个so只能由一个classLoader加载
if (pEntry->classLoader != classLoader) {
ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
pathName, pEntry->classLoader, classLoader);
return false;
}
if (verbose) {
ALOGD("Shared lib '%s' already loaded in same CL %p",
pathName, classLoader);
}
if (!checkOnLoadResult(pEntry))
return false;
return true;
}
Thread* self = dvmThreadSelf();
ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
// 打开so动态库文件,创建一个handle
handle = dlopen(pathName, RTLD_LAZY);
dvmChangeStatus(self, oldStatus);
if (handle == NULL) {
*detail = strdup(dlerror());
ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
return false;
}
/* create a new entry */
SharedLib* pNewEntry;
pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
pNewEntry->pathName = strdup(pathName);
pNewEntry->handle = handle;
pNewEntry->classLoader = classLoader;
dvmInitMutex(&pNewEntry->onLoadLock);
pthread_cond_init(&pNewEntry->onLoadCond, NULL);
pNewEntry->onLoadThreadId = self->threadId;
/* try to add it to the list */
//将SharedLib添加到哈希表中
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
if (pNewEntry != pActualEntry) {
ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
pathName, classLoader);
freeSharedLibEntry(pNewEntry);
return checkOnLoadResult(pActualEntry);
} else {
if (verbose)
ALOGD("Added shared lib %s %p", pathName, classLoader);
bool result = true;
void* vonLoad;
int version;
// 查看so是否有JNI_OnLoad函数,有并调用这个函数
vonLoad = dlsym(handle, "JNI_OnLoad");
// vonLoad==NULL 说明so里没有JNI_OnLoad方法
if (vonLoad == NULL) {
ALOGD("No JNI_OnLoad found in %s %p, skipping init",
pathName, classLoader);
} else {
/*
* Call JNI_OnLoad. We have to override the current class
* loader, which will always be "null" since the stuff at the
* top of the stack is around Runtime.loadLibrary(). (See
* the comments in the JNI FindClass function.)
*/
OnLoadFunc func = (OnLoadFunc)vonLoad;
Object* prevOverride = self->classLoaderOverride;
self->classLoaderOverride = classLoader;
oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
if (gDvm.verboseJni) {
ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
}
// 加载JNI_OnLoad方法
version = (*func)(gDvmJni.jniVm, NULL);
dvmChangeStatus(self, oldStatus);
self->classLoaderOverride = prevOverride;
if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
version != JNI_VERSION_1_6)
{
ALOGW("JNI_OnLoad returned bad version (%d) in %s %p",
version, pathName, classLoader);
/*
* It's unwise to call dlclose() here, but we can mark it
* as bad and ensure that future load attempts will fail.
*
* We don't know how far JNI_OnLoad got, so there could
* be some partially-initialized stuff accessible through
* newly-registered native method calls. We could try to
* unregister them, but that doesn't seem worthwhile.
*/
result = false;
} else {
if (gDvm.verboseJni) {
ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName);
}
}
}
if (result)
pNewEntry->onLoadResult = kOnLoadOkay;
else
pNewEntry->onLoadResult = kOnLoadFailed;
pNewEntry->onLoadThreadId = 0;
/*
* Broadcast a wakeup to anybody sleeping on the condition variable.
*/
dvmLockMutex(&pNewEntry->onLoadLock);
pthread_cond_broadcast(&pNewEntry->onLoadCond);
dvmUnlockMutex(&pNewEntry->onLoadLock);
return result;
}
}
以上步骤:
1.检查so是否已经加载过,如果是检查classLoader是否相同,一个so只能由一个classLoader加载
2.打开so动态库文件,创建一个handle
3.将SharedLib添加到哈希表中
4.查看so是否有JNI_OnLoad函数,有并调用这个函数,vonLoad==NULL 说明so里没有JNI_OnLoad方法
5.有JNI_OnLoad函数这时就会去加载JNI_OnLoad方法
3.2.2 JNI_OnLoad过程
JNI_OnLoad
---RegisterNatives
--------dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)
--------------dvmUseJNIBridge(method, fnPtr)-> (method->nativeFunc = func)
最终将JNI函数起始地址保存到ClassObject结构体directMethods中。
4.ClassObject结构体
struct ClassObject : Object {
/* static, private, and methods */
int directMethodCount;
Method* directMethods;
/* virtual methods defined in this class; invoked through vtable */
int virtualMethodCount;
Method* virtualMethods;
}