Android 類加載器

對于 Android 而言,最終的 apk 文件包含的是 dex 類型的文件,dex 文件是將 class 文件重新打包,打包的規(guī)則又不是簡單地壓縮,而是完全對 class 文件內(nèi)部的各種函數(shù)表,變量表進(jìn)行優(yōu)化,產(chǎn)生一個新的文件,即 dex 文件。而加載這種特殊的 Class 文件就需要特殊的類加載器 DexClassLoader。

源碼位于 AOSP/libcore/dalvik/src/main/java/dalvik/system 目錄下。

BaseDexClassLoader

/**
 * Base class for common functionality between various dex-based
 * {@link ClassLoader} implementations.
 */
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // First, check whether the class is present in our shared libraries.
        if (sharedLibraryLoaders != null) {
            for (ClassLoader loader : sharedLibraryLoaders) {
                return loader.loadClass(name);
            }
        }
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        return c;
    }

    public void addDexPath(String dexPath) {
        addDexPath(dexPath, false /*isTrusted*/);
    }

    public void addDexPath(String dexPath, boolean isTrusted) {
        pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted);
    }

    @Override
    protected URL findResource(String name) {
        if (sharedLibraryLoaders != null) {
            for (ClassLoader loader : sharedLibraryLoaders) {
                URL url = loader.getResource(name);
                if (url != null) {
                    return url;
                }
            }
        }
        return pathList.findResource(name);
    }
}
  1. 在構(gòu)造函數(shù)中初始化 DexPathList;
  2. 通過 DexPathList 查找 Class 和 資源;

DexPathList

/**
 * A pair of lists of entries, associated with a {@code ClassLoader}.
 * One of the lists is a dex/resource path &mdash; typically referred
 * to as a "class path" &mdash; list, and the other names directories
 * containing native code libraries. Class path entries may be any of:
 * a {@code .jar} or {@code .zip} file containing an optional
 * top-level {@code classes.dex} file as well as arbitrary resources,
 * or a plain {@code .dex} file (with no possibility of associated
 * resources).
 *
 * <p>This class also contains methods to use these lists to look up
 * classes and resources.</p>
 */
/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";
    private Element[] dexElements;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
            ...
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext);
        ...
    }

    /**
     * Makes an array of dex/resource path elements, one per element of
     * the given array.
     */
    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  if (dex != null) {
                      elements[elementsPos++] = new Element(dex, null);
                  }
              } else {
                  DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

    /**
     * Constructs a {@code DexFile} instance, as appropriate depending on whether
     * {@code optimizedDirectory} is {@code null}. An application image file may be associated with
     * the {@code loader} if it is not null.
     */
    private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file, loader, elements);
        } else {
            // 間接調(diào)用 new DexFile()
            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
        }
    }

    static class Element {
        /**
         * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
         * should be null), or a jar (in which case dexZipPath should denote the zip file).
         */
        @UnsupportedAppUsage
        public Element(DexFile dexFile, File dexZipPath) {
            this.dexFile = dexFile;
            this.path = dexZipPath;
        }

        public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null;
        }

        public URL findResource(String name) {
            // We support directories so we can run tests and/or legacy code
            // that uses Class.getResource.
            if (path != null && path.isDirectory()) {
                File resourceFile = new File(path, name);
                if (resourceFile.exists()) {
                    return resourceFile.toURI().toURL();
                }
            }
            return null;
        }
    }
}

DexPathList 通過 makeDexElements() 方法返回 Element[] 數(shù)組,Element 中保存有 DexFile 的對象。

DexFile

/**
 * Loads DEX files. This class is meant for internal use and should not be used by applications.
 */
@Deprecated
public final class DexFile {
    DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        mCookie = openDexFile(fileName, null, 0, loader, elements);
    }

    private static Object openDexFile(String sourceName, String outputName, int flags, ...) throws IOException {
        return openDexFileNative(...);
    }

    /*
     * Open a DEX file.  The value returned is a magic VM cookie.  On
     * failure, an IOException is thrown.
     */
    private static native Object openDexFileNative(String sourceName, String outputName, int flags, ...);

    static DexFile loadDex(String sourcePathName, String outputPathName,
        int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
    }

    public Class loadClass(String name, ClassLoader loader) {
        String slashName = name.replace('.', '/');
        return loadClassBinaryName(slashName, loader, null);
    }

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = defineClassNative(name, loader, cookie, dexFile);
        return result;
    }

    private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
}
  1. 在構(gòu)造方法中處理 Dex 文件;
  2. 通過 native 方法打開 Dex 文件和定義 Class。

JNI 實現(xiàn)文件為 AOSP/art/runtime/native/ 文件夾下。

// dalvik_system_DexFile.cc
static jobject DexFile_openDexFileNative(JNIEnv* env, jclass, ...) {
  Runtime* const runtime = Runtime::Current();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  const OatFile* oat_file = nullptr;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), class_loader, dex_elements, ...);
  if (!dex_files.empty()) {
    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
    return array;
  } else {
    return nullptr;
  }
}

關(guān)鍵是 OpenDexFilesFromOat() 方法,轉(zhuǎn)到 oat_file_manager.cc 文件。

// oat_file_manager.cc
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location,
    jobject class_loader, jobjectArray dex_elements, ...) {

    Runtime* const runtime = Runtime::Current();
    OatFileAssistant oat_file_assistant(dex_location, kRuntimeISA, !runtime->IsAotCompiler());
    // Get the oat file on disk.
    std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());
    if (oat_file != nullptr) {
        // Take the file only if it has no collisions, or we must take it because of preopting.
        bool accept_oat_file =
            !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg);
        if (accept_oat_file) {
            source_oat_file = RegisterOatFile(std::move(oat_file));
            *out_oat_file = source_oat_file;
        }
    }

    std::vector<std::unique_ptr<const DexFile>> dex_files;

    // Load the dex files from the oat file.
    if (source_oat_file != nullptr) {
        bool added_image_space = false;
        if (source_oat_file->IsExecutable()) {
            std::unique_ptr<gc::space::ImageSpace> image_space =
                kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr;
            runtime->GetHeap()->AddSpace(image_space.get());
            added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(), ...);
        }
        if (!added_image_space) {
            dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
        }
    }

    return dex_files;
}

主要是將 dex 文件轉(zhuǎn) oat。如果轉(zhuǎn) oat 失敗,那就打開 dex 的方式來加載。

DexClassLoader

能夠加載未安裝的 jar、apk 或 dex。

/**
 * 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 {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native code.  Interpreted classes are found in a set of DEX files contained in Jar or APK files.
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

DexClassLoader 繼承 BaseDexClassLoader,只有一個構(gòu)造函數(shù),optimizedDirectory 傳 null。

PathClassLoader

只能從本地文件系統(tǒng)加載,不能從網(wǎng)絡(luò)加載類。

/**
 * 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 librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

參考

[1] DexClassLoader - developer
[2] PathClassLoader - developer
[3] Android 類加載器 ClassLoader - Gityuan 博客
[4] Android 11 在線源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容