前言
最近在看 Flutter
中 Dart
和 Java
使用 MethodChannel
进行通信相关的代码,有上层一直跟到了底层。最后看到了 MethodChannel
的注册是在 JNI_OnLoad
的方法中。这个方法是在 so
被加载的时候调用的。今天主要从so
的加载看一下 JNI_OnLoad
的调用。
Flutter的so加载
我们先从 Application
的代码看起:
FlutterApplication.onCreate;
|--FlutterMain.startInitialization(Context applicationContext);
|--|--FlutterMain.startInitialization(Context applicationContext, FlutterMain.Settings settings);
public static void startInitialization(Context applicationContext, FlutterMain.Settings settings) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
} else if (sSettings == null) {
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
}
这 startInitialization
我们调用了 System.loadLibrary("flutter")
进行 flutter
的 so
加载。
so的加载
AndroidP源码:
System.loadLibrary(libName);
|--Runtime.loadLibrary0(libName,classLoader);
|--|--|--Runtime.nativeLoad(name,loader,ldLibraryPath);
|--|--|--Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath);
|--|--|--|--JVM_NativeLoad(env, javaFilename, javaLoader);
|--|--|--|--JavaVMExt::LoadNativeLibrary(const std::string& path,Handle class_loader,std::string* detail)
|--|--|--|--|--android::OpenNativeLibrary(dlopen);
|--|--|--|--|--|--SharedLibrary.FindSymbol;
|--|--|--|--|--|--SharedLibrary.FindSymbolWithoutNativeBridge(dlsym);
bool JavaVMExt::IsBadJniVersion(int version) {
// We don't support JNI_VERSION_1_1. These are the only other valid versions.
return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
}
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
std::string* error_msg) {
/******部分代码省略******/
bool was_successful = false;
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
ScopedLocalRef old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
//定义一个名为JNI_OnLoadFn的函数指针,参数为(JavaVM*, void*),返回值类型为int
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
//将JNI_OnLoadFn指向sym也就是JNI_OnLoad函数
JNI_OnLoadFn jni_on_load = reinterpret_cast(sym);
//调用JNI_OnLoad方法,返回jni版本号
int version = (*jni_on_load)(this, nullptr);
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
EnsureFrontOfChain(SIGSEGV);
}
self->SetClassLoaderOverride(old_class_loader.get());
if (version == JNI_ERR) {//JNI_ERR=-1
StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
} else if (JavaVMExt::IsBadJniVersion(version)) {//错误的jni版本
StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
path.c_str(), version);
} else {
was_successful = true;
}
VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
<< " from JNI_OnLoad in \"" << path << "\"]";
}
library->SetResult(was_successful);
return was_successful;
}
上面这个过程我们证明了 so
的加载会调用 JNI_OnLoad
。 其中 Runtime_nativeLoad
方法是通过动态注册实现的。
相同的我们也可以看 JNI_OnUnload
方法,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。
dlopen、dlsym
使用dlopen,dlsym调用JNI_OnLoad方法;
- dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程;
- dlerror返回出现的错误;
- dlsym通-过句柄和连接符名称获取函数名或者变量名;
- dlclose来卸载打开的库;
dlfcn.c
文件:
#ifndef DLFCN_H
#define DLFCN_H
#ifdef __cplusplus
extern "C" {
#endif
#if defined(DLFCN_WIN32_EXPORTS)
# define DLFCN_EXPORT __declspec(dllexport)
#else
# define DLFCN_EXPORT
#endif
#define RTLD_LAZY 0
#define RTLD_NOW 0
#define RTLD_GLOBAL (1 << 1)
#define RTLD_LOCAL (1 << 2)
#define RTLD_DEFAULT ((void *)0)
#define RTLD_NEXT ((void *)-1)
DLFCN_EXPORT void *dlopen ( const char *file, int mode );
DLFCN_EXPORT int dlclose(void *handle);
DLFCN_EXPORT void *dlsym(void *handle, const char *name);
DLFCN_EXPORT char *dlerror(void);
#ifdef __cplusplus
}
#endif
#endif /* DLFCN_H */
native方法的动态注册
前面我们就有讲过在 so
被加载之后会调用 JNI_OnLoad
方法,我们这次反过来看一下 JNI_OnLoad
加载 native
方法。
在 /libcore/ojluni/src/main/native/Register.cpp 文件中有一个 JNI_OnLoad
方法,它在内部进行了 register_java_lang_Runtime(env);
的注册。register_java_lang_Runtime
的实现在 /libcore/ojluni/src/main/native/Runtime.c 文件中:
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader){
return JVM_NativeLoad(env, javaFilename, javaLoader);
}
static JNINativeMethod gMethods[] = {
FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
NATIVE_METHOD(Runtime, gc, "()V"),
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
//java.lang.Runtime.nativeLoad方法的native注册
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;)"
"Ljava/lang/String;"),
};
void register_java_lang_Runtime(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
总结
从上面的过程分析我们看到了 so
的加载以及它的注册 JNI_OnLoad
和反注册 JNI_OnUnload
方法的调用,以及 native
方法的注册。