从JNI_OnLoad看so的加载

前言

最近在看 FlutterDartJava 使用 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") 进行 flutterso 加载。

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<mirror::ClassLoader> 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<jobject> 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<JNI_OnLoadFn>(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 方法的注册。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 77,383评论 1 169
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 25,958评论 1 141
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 28,731评论 0 100
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 16,068评论 0 86
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 21,280评论 0 144
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 17,523评论 0 87
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 10,350评论 2 161
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 9,746评论 0 76
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 8,245评论 5 109
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 11,526评论 0 127
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 10,297评论 1 123
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 11,100评论 0 127
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 5,962评论 0 17
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 8,760评论 2 114
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 11,824评论 3 121
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 7,613评论 0 3
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 7,815评论 0 75
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 12,263评论 2 132
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 12,946评论 2 130

推荐阅读更多精彩内容