• BaseDexClassLoader的正确使用方式


    前言:

    今天解决一个插件化问题的时候,竟然发现SO没有正常加载,很怪异,最终排查下来发现竟然是参数传入错误导致的。这就扯到了本文的标题上了,BaseDexClassLoader中的4个参数该如何传入,传入的又是什么呢?

    一共有4个参数,分来来讲。

    1:dexFile(String类型)
    2:optimizedDirectory(File类型)
    3:librarySearchPath(String类型)
    4:parent(ClassLoader类型)
    

    一.dexPath(String)

    官方的注释:

    1. * @param dexPath the list of jar/apk files containing classes and
    2. * resources, delimited by {@code File.pathSeparator}, which
    3. * defaults to {@code ":"} on Android.

    包含类和类的jar/apk文件列表资源,由{@code File.pathSeparator}分隔,其中Android上的默认值为{@code”:“}。

    也就是说这里其实是可以传入一个集合的。

    比如如下的参数都是可以被接受的:

    sdcard/test/aa.apk

    sdcard/test/aa.apk:sdcard/test/bb.apk:sdcard/test/cc.apk

    sdcard/test/aa.apk:sdcard/test/dd.jar

    其中分隔符:,保险起见,可以使用File.pathSeparator替代。

    示例代码如下:

    1. private void loadDex(List apkList) {
    2. StringBuilder builder = new StringBuilder();
    3. for (File f : apkList) {
    4. builder.append(f.getAbsolutePath());
    5. builder.append(File.separatorChar);
    6. }
    7. DexClassLoader dexClassLoader = new DexClassLoader(builder.toString(), null, null, getClass().getClassLoader());
    8. }

    二.optimizedDirectory

    官方的注释:

    this parameter is deprecated and has no effect since API level 26.

    解压的路径,这里传入路径的最主要目的就是为了生成odex文件夹,方便后续存储odex文件。

    如注释中所写,这个参数26开始已经失效了。所以这里就不扩展去讲了。

    三.librarySearchPath

    官方的注释:

     * @param librarySearchPath the list of directories containing native

    包含native目录的目录列表,这里要注意的,传入的一定是so的上一级目录才可以。如果是更上一层的目录是不行的。前言中的问题就出在这,传入了一个更上一层的目录地址。

    排查流程:

    最终使用librarySearchPath的地方是在DexPathList的splitPaths方法中。生成File加入List中:

    1. @UnsupportedAppUsage
    2. private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
    3. List<File> result = new ArrayList<>();
    4. if (searchPath != null) {
    5. for (String path : searchPath.split(File.pathSeparator)) {
    6. ...
    7. result.add(new File(path));
    8. }
    9. }
    10. return result;
    11. }

    而使用的时候,是使用了findLibrary的方法,for循环便利上面集合中的所有path,看是否存在。

    1. public String findLibrary(String libraryName) {
    2. String fileName = System.mapLibraryName(libraryName);
    3. for (NativeLibraryElement element : nativeLibraryPathElements) {
    4. String path = element.findNativeLibrary(fileName);
    5. if (path != null) {
    6. return path;
    7. }
    8. }
    9. return null;
    10. }

    而寻找的最终会调用到NativeLibraryElement的findNativeLibrary方法:

    1. public String findNativeLibrary(String name) {
    2. maybeInit();
    3. if (zipDir == null) {
    4. String entryPath = new File(path, name).getPath();
    5. if (IoUtils.canOpenReadOnly(entryPath)) {
    6. return entryPath;
    7. }
    8. } else if (urlHandler != null) {
    9. // Having a urlHandler means the element has a zip file.
    10. // In this case Android supports loading the library iff
    11. // it is stored in the zip uncompressed.
    12. String entryName = zipDir + '/' + name;
    13. if (urlHandler.isEntryStored(entryName)) {
    14. return path.getPath() + zipSeparator + entryName;
    15. }
    16. }
    17. return null;
    18. }

    这段代码也就是问题的核心了,直接使用了new File(path,name)的方式,而不是循环查找,所以只有上一层才可以。

    四.parent

    官方的注释:

    @param parent the parent class loader

    这个比较简单,就是上一层的classLoader。

    五.总结

    无论是dexPath还是librarySearchPath,都是支持多路径传入的。路径之间使用File.pathSeparator进行分隔。dexPath中必须是APK或者Jar包的路径,而librarySearchPath中必须是so文件的上一层级文件夹才可以。

  • 相关阅读:
    解决错误代码0x0000011b的方法,教你一键修复0x0000011b
    Vue3新特性--学习笔记
    用户生命周期价值LTV
    Ubuntu server 24 (Linux) 安装lua + 卸载软件
    leetcode:189. 轮转数组(python3解法)
    MySQL开窗函数
    6277: 【区赛】【鄞州2022】最小差值
    php遍历某月份所有日期的实例
    python 设计模式 观察者模式(发布订阅模式)
    hash:哈希表 哈希桶
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/126228474