ClassLoader (二) — 浅析 PathClassLoader 和 DexClassLoader 的区别

一、概述

在 Android 中接触最多的 ClassLoader 是 PathClassLoader 和 DexClassLoader,那么它们之间有什么区别呢?

结论:

Android 8.0 之前:
相同点: PathClassLoaderDexClassLoader 都能加载外部的 dex、jar、apk

差异: PathClassLoader 和 DexClassLoader 是否能指定 optimizedDirectory 路径 (即 dex2oat 的产物 .odex 存放的位置)。

  1. DexClassLoader 可以指定 optimizedDirectory (即 dex2oat 的产物 .odex 存放的位置),
  2. PathClassLoader 只能使用系统默认位置 (即 /data/dalvik-cache/xxx.odex)。

Android 8.0 之后:

  1. PathClassLoaderDexClassLoader 无差异,都能加载外部的 dex、jar、apk

版本: Android 5.0

由于 Android 8.0 后两者无差异,为了分析两者的差异,我们基于 Android 5.0 代码来分析。
在线源码 https://www.androidos.net.cn/sourcecode


二、 PathClassLoader 和 DexClassLoader 构造方法的区别

由于 PathClassLoader 和 DexClassLoader 类加载器都是继承自 BaseDexClassLoader ,所以在分析 PathClassLoader 和 DexClassLoader 之前先来看一下 BaseDexClassLoader 构造方法中几个参数的含义。

BaseDexClassLoader

BaseDexClassLoader 构造方法中四个参数的含义:
dexPath:需要加载的文件列表 (文件可以是包含了 classes 和 resources 的 JAR、APK),多个文件用 “:” 分割。
optimizedDirectory:dex 经过 dex2oat 优化后,生成 .odex 文件的存放路径,可以为 null 。
libraryPath:存放需要加载的 native 库 (即.so库)的目录。
parent:父 ClassLoader。
通过构造函数我们大概可以了解到 BaseDexClassLoader 的运行方式,传入 dex 文件,然后进行优化,保存优化后的 dex 文件到 optimizedDirectory 目录。

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        // 这里将 optimizedDirectory 传入了 DexPathList 中。
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

下面来分别看下 DexClassLoader 和 PathClassLoader 构造方案的差异。

DexClassLoader

由 DexClassLoader 类的注释我们可以看出,DexClassLoader 可以执行没有经过安装的应用程序代码。

/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
 */
public class DexClassLoader extends BaseDexClassLoader {

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        // 此处将 optimizedDirectory 传入父类构造器中。
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

PathClassLoader

由 PathClassLoader 类的注释我们可以看出,它用来操作文件系统上的一系列文件和目录 的 ClassLoader 实现,但并未说明只能加载已安装应用的代码文件。

/**
 * Provides a simple {@link ClassLoader} implementation that operates on a list
 * of files and directories in the local file system, but does not attempt to
 * load classes from the network. Android uses this class for its system class
 * loader and for its application class loader(s).
 */
public class PathClassLoader extends BaseDexClassLoader {

    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
    	// // 此处第二个参数代表 optimizedDirectory,传入null,使用默认的路径(即系统指定的路径)。
        super(dexPath, null, libraryPath, parent);
    }
}

小结:

经过上面部分,我们知道 DexClassLoader 和 PathClassLoader 的差异在于构造 ClassLoader 对象时,是否给父类 (BaseDexClassLoader) 传递 optimizedDirectory 参数 (即是否指定优化后的 .odex 文件的存储路径)。


三、optimizedDirectory 参数的使用

在上面我们分析了 PathClassLoader 和 DexClassLoader 的区别在于参数 optimizedDirectory 是否赋值,接下去我们分析一下optimizedDirectory 参数的使用流程。

1. DexPathList 中的流程

在 BaseDexClassLoader 构造方法中,我们知道 optimizedDirectory 参数传递给了 DexPathList 对象,下面我们来分析一下 DexPathList。

DexPathList

optimizedDirectory 参数在 DexPathList 中传递流程如下:DexPathList 构造 -> makeDexElements -> loadDexFile

final class DexPathList {
	// 省略代码

    // 保存 dex 和资源列表
    private final Element[] dexElements;
    // 保存 .so 库
    private final File[] nativeLibraryDirectories;

	// 省略代码
    
    public DexPathList(ClassLoader definingContext, String dexPath, 
    		String libraryPath, File optimizedDirectory) {
        // 省略代码
        // 这里将 optimizedDirectory 传递给了 makeDexElements 方法。
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
        
        // 省略代码
    }

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        // 将 optimizedDirectory 参数传递给 loadDexFile 方法
        DexFile  dex = loadDexFile(file, optimizedDirectory);
        // 省略代码
    }

    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
        	// optimizedDirectory 如果不存在,直接创建 DexFile 对象。
            return new DexFile(file);
        } else {
        	// 将dex/jar文件路径和输出目录转换为关联的优化dex文件的输出文件路径。
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            // 将优化后的文件路径传入 DexFile 中。
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }
}

小结:
DexPathList 中,optimizedDirectory 参数只是在各个方法中做了透传,最终传递给了 DexFile 对象。

2. DexFile 中的流程

DexFile

optimizedDirectory 参数在 DexFile 中传递流程如下:DexFile 构造 -> openDexFile -> openDexFileNative

public final class DexFile {

    static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException {
    	// 构建 DexFile 对象,并传递 outputPathName 参数。
        return new DexFile(sourcePathName, outputPathName, flags);
    }
    
    public DexFile(File file) throws IOException {
        this(file.getPath());
    }

    public DexFile(String fileName) throws IOException {
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
    }


    private DexFile(String sourceName, String outputName, int flags) throws IOException {
        if (outputName != null) {
           // 省略代码
        }
        // 打开 dexFile 文件
        mCookie = openDexFile(sourceName, outputName, flags);
        mFileName = sourceName;
        guard.open("close");
    }

    private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
        // 最终会执行到 Native 代码逻辑。
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                 (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                                 flags);
    }

    private static native long openDexFileNative(String sourceName, String outputName, int flags);
}

小结:
创建 DexFile 对象的方式有两种:

  1. optimizedDirectory 参数为空时,直接创建 DexFile 对象。
  2. optimizedDirectory 参数不为空时,通过 DexFile.loadDex() 方法创建 DexFile 对象,并将 optimizedDirectory 转换后的参数传入 DexFile 。

3. Native 层的流程

在 DexFile 中,最终会执行 openDexFileNative 方法调用对应的 native 逻辑。对应的 Native 逻辑为 dalvik_system_DexFile.cc 的 DexFile_openDexFileNative 方法。
ClassLinker->OpenDexFilesFromOat 逻辑在 class_linker.cc 中

https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc

在 DexFile_openDexFileNative 里主要做事情是处理 DEX 文件,并生成 .odex 文件到 optimizedDirectory 里。

dalvik_system_DexFile.cc

static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  // 省略代码
  
  NullableScopedUtfChars outputName(env, javaOutputName);
  if (env->ExceptionCheck()) {
    return 0;
  }

  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
  std::vector<std::string> error_msgs;
  // ClassLinker->OpenDexFilesFromOat 逻辑在 class_linker.cc 中
  bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
                                             dex_files.get());
  // 省略代码
}

class_linker.cc

// oat_location 就是我们从 java 层传递进来的 optimizedDirectory 参数。 
bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
                                      std::vector<std::string>* error_msgs,
                                      std::vector<const DexFile*>* dex_files) {
  // 省略代码

  // Look in cache location if no oat_location is given.
  std::string cache_location;
  // oat_location 就是我们传入的 optimizedDirectory 参数。为空时就使用默认缓存的路径。
  if (oat_location == nullptr) {
    // Use the dalvik cache.
    // GetInstructionSetString方法在 https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc 中。
    const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
    cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
    // 将默认的路径赋值给 oat_location。
    oat_location = cache_location.c_str();
  }
  
  // 省略代码

  if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
    // Create the oat file.
    // 创建 OAT 文件到指定的路径下 (即 oat_location)。
    open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
                                                    oat_location, error_msgs));
  }
}

utils.cc (https://www.androidos.net.cn/android/5.0.1_r1/xref/art/runtime/utils.cc)

该方法返回默认的路径:/data/dalvik-cache/

std::string GetDalvikCacheOrDie(const char* subdir, const bool create_if_absent) {
  CHECK(subdir != nullptr);
  const char* android_data = GetAndroidData();
  const std::string dalvik_cache_root(StringPrintf("%s/dalvik-cache/", android_data));
  const std::string dalvik_cache = dalvik_cache_root + subdir;
  if (create_if_absent && !OS::DirectoryExists(dalvik_cache.c_str())) {
    // Don't create the system's /data/dalvik-cache/... because it needs special permissions.
    if (strcmp(android_data, "/data") != 0) {
      int result = mkdir(dalvik_cache_root.c_str(), 0700);
      if (result != 0 && errno != EEXIST) {
        PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache_root;
        return "";
      }
      result = mkdir(dalvik_cache.c_str(), 0700);
      if (result != 0) {
        PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache;
        return "";
      }
    } else {
      LOG(FATAL) << "Failed to find dalvik-cache directory " << dalvik_cache;
      return "";
    }
  }
  return dalvik_cache;
}

4. 小结

optimizedDirectory 参数的传递流程如下:
在这里插入图片描述


四、Android 8.0 的版本

BaseDexClassLoader

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent, boolean isTrusted) {
    super(parent);
    // optimizedDirectory 参数没有传递给 DexPathList。
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

    if (reporter != null) {
        reportClassLoaderChain();
    }
}

五、参考

  1. 谈谈 Android 中的 PathClassLoader 和 DexClassLoader
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值