System.loadLibrary分析

System.loadLibrary

在加载so库的时候,我们最常用的就是loadLibrary()这个函数,我们也知道load()传递的是so文件的绝对路径,但是loadLibrary是如何找到so的呢,今天就去看下这后面发生了什么。

public static void loadLibrary(String libname) {
    Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}  

到了这里,是不是有点疑惑了loadLibrary0()这个函数好理解,libname也好理解,VMStack.getCallingClassLoader()返回的究竟是哪个ClassLoader呢?其实这里返回的是启动这个System.loadLibrary函数调用处所在类的加载器。这样说的有点拗口,举个栗子,在类A中调用了loadLibrary()这个函数,那么VMStack.getCallingClassLoader()返回的就是类A的classloader。好的,进入loadLibrary0()这个函数内部,看看后面发生了什么。

1. so文件的查找

synchronized void loadLibrary0(ClassLoader loader, String 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<String> candidates = new ArrayList<String>();
    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);
}

正常情况下,我们传的loader是非空的,很少loader会出现空的情况,故此处我们重点分析load不为空的情况。loader不为空的情况下,首先就调用了loader.findLibrary这个函数,函数的参数是我们传递的so库的名称。这下就要进入loader内部分析了,我们分析DexClassLoader中的findLibrary这个函数。基于android classloader的继承体系,findLibrary这个函数最终在BaseDexClassLoader中实现。

@Override
public String findLibrary(String name) {
    return pathList.findLibrary(name);
}  

findLibrary最终会调用pathList的findLibrary这个函数,pathList是什么鬼?我们看下BaseDexClassLoader函数的构造函数,顺便贴下DexClassLoader的构造函数,理解起来会更容易:

public DexClassLoader(String dexPath, String optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}


public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}  

可以发现我们在自定义DexClassLoader的时候传递了我们的dexPath(也就是apk文件的路径),optimizedDirectory(odex文件的目录),librarySearchPath(so文件的目录),parent(父加载器);在构造DexPathList对象时传递了dexPath,librarySearchPath,optimizedDirectory。ok,我们到DexPathList里看下findLibrary这个函数的具体实现。

public String findLibrary(String libraryName) {
    String fileName = System.mapLibraryName(libraryName);

    for (Element element : nativeLibraryPathElements) {
        String path = element.findNativeLibrary(fileName);

        if (path != null) {
            return path;
        }
    }

    return null;
}  

可以看到首先调用了System.mapLibraryName(libraryName)这个函数。这个函数的字面意思感觉是要把我们传递的libraryName映射为另外一个名字,我去!!!感觉这可能就是为什么so文件的名字是libxx.so,但是我们穿xx的时候系统仍然能找到的原因吧。到这里我么就必须要看下System.mapLibraryName(libraryName)这个函数的具体实现了。哎,又是一个native实现,智能去扒扒native的源码了。这个方法的具体实现在/libcore/ojluni/src/main/native/System.c中。

JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);

    jchar chars[256];
    if (libname == NULL) {
    JNU_ThrowNullPointerException(env, 0);
    return NULL;
    }
    len = (*env)->GetStringLength(env, libname);
    if (len > 240) {
    JNU_ThrowIllegalArgumentException(env, "name too long");
    return NULL;
    }
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    len += prefix_len;
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    len += suffix_len;
    return (*env)->NewString(env, chars, len);
}  

分析这部分源码其实可以发现,就是在libname的前面和后面添加了JNI_LIB_PREFIX和JNI_LIB_SUFFIX,再看下这两个常量是如何定义的。这两个常量在/libcore/ojluni/src/main/native/jvm_md.h中定义:

#define JNI_LIB_PREFIX "lib"
#define JNI_LIB_SUFFIX ".so"  

到这里终于弄明白了System.mapLibraryName这个函数的作用,就是讲我们传的xx变为libxx.so。

接下来继续分析DexPathList findLibrary函数的剩余部分:

for (Element element : nativeLibraryPathElements) {
    String path = element.findNativeLibrary(fileName);

    if (path != null) {
        return path;
    }
}  

nativeLibraryPathElements这个变量我就不贴代码了,这个变量记录了应用查找so文件的路径,这个路径包括系统路径/vendor/lib//system/lib/以及我们创建classload时定义的so文件搜索路径。如果能在任意路径找到需要的so文件,则返回so文件的绝对路径。
以上就是so文件查找的过程。

2. so文件的加载

在第一节中系统找到了so文件的绝对路径,接下来系统会根据so文件的绝对路径加载so文件。

doLoad(filename, loader);  

最终加载的函数就是doLoad(dilename, loader),这个函数还没有研究过,后面再说这个函数。到这里大家可能会联想到另一个加载so文件的函数System.load(),这个函数相较于System.loadLibrary()函数而言,参数值时so文件的绝对路径,不需要进行so文件路径的查找。

好的,so文件的加载过程就到这了。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值