Android 中的類加載器

ClassLoader 的類型

  • java 中的ClassLoader 加載的是jar 和class文件
  • Android 中的ClassLoader 加載的是dex文件

兩種ClassLoader類型

  • 系統類加載器
  • 自定義加載器
系統類加載器 (3種)
  • BootClassLoader
  • PathClassLoader
  • DexClassLoader
BootClassLoader
  • Android 系統啓動時會使用BootClassLoader來預加載常用類,而BootClassLoader是由java實現的
  • java 中的BootstrapClassLoader 是由C/C++代碼實現的
BootClassLoader 代碼
class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    ......
  • BootClassLoader 是 ClassLoader 的內部類
  • BootClassLoader 繼承自ClassLoader
  • BootClassLoader是個單例類
  • BootClassLoader 的訪問修飾符是默認的,只有在同一個包中纔可以訪問,
DexClassLoader
用途
  • DexClassLoader 可以加載dex文件
  • DexClassLoader也可以加載包含dex的壓縮文件(apk 和jar文件)
DexClassLoader源碼如下
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.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     *     should be written; must not be {@code null}
     * @param libraryPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
    *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

DexClassLoader繼承自BaseDexClassLoader,方法都在BaseDexClassLoader中實現

DexClassLoader 的構造方法有如下4個參數

  • dexPath:dex相關文件路徑集合,多個路徑用文件分隔符分隔,默認文件分隔符爲":"
  • optimizedDirectory:解壓的dex文件存儲路徑,這個路徑必須是一個內部存儲路徑:如/data/data//…
  • librarySearchPath:包含C/C++庫的路徑集合,多個路徑用文件分隔符分隔,可以爲null
  • parent:父加載器
PathClassLoader
用途:
  • PathClassLoader 用來加載系統類和應用程序的類
PathClassLoader的代碼
/**
 * 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 {
    /**
     * Creates a {@code PathClassLoader} that operates on a given list of files
     * and directories. This method is equivalent to calling
     * {@link #PathClassLoader(String, String, ClassLoader)} with a
    * {@code null} value for the second argument (see description there).
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    /**
     * Creates a {@code PathClassLoader} that operates on two given
     * lists of files and directories. The entries of the first list
     * should be one of the following:
    *
    * <ul>
     * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
     * well as arbitrary resources.
     * <li>Raw ".dex" files (not inside a zip file).
     * </ul>
    *
     * The entries of the second list should be directories containing
     * native library files.
     *
     * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}
  • PathClassLoader 繼承自BaseDexClassLoader,也都在BaseDexClassLoader中實現
  • 在PathClassLoader的構造方法中沒有參數optimizedDirectory,這是因爲PathClassLoader已經默認了參數optimizedDirectory的值爲/data/dalvik-cache
  • PathClassLoader無法自定義解壓dex文件存儲路徑
  • PathClassLoader通常用來加載已經安裝的apk的dex文件(安裝的apk的dex文件會存儲在/data/dalvik-cache中)

ClassLoader 的繼承關係

先看圖再解釋
在這裏插入圖片描述

  • ClassLoader 是一個抽象類,其中定義了ClassLoader的主要功能,BootClassLoader是它的內部類
  • SecureClassLoader 類和JDK8中的SecureClassLoaderL類的代碼是一樣的,它集成了抽象類ClassLoader,SecureClassLoader 並不是ClassLoader 的實現類,而是擴展了ClassLoader類加入了權限方面的功能,加強了ClassLoader的安全性
  • URLClassLoader 類和JDK8 中的URLClassLoader類代碼是一樣的,它繼承自SecureClassLoader,用來通過URL路徑從jar文件和文件夾中加載類和資源
  • InMemoryDexClassLoader 是Android 8.0 新增的類加載器,繼承自BaseDexClassLoader,用於加載內存中的dex文件
  • BaseDexClassLoader 繼承自ClassLoader ,是抽象類ClassLoader的具體實現類,PathClassLoader,DexClassLoader和InMemoryDexClassLoader 都繼承自它

ClassLoader 的加載過程

  • Android 的ClassLoader 同樣遵循雙親委託模式
  • ClassLoader的加載方法爲 loadClass
  • loadClass方法被定義在抽象類ClassLoader中
ClassLoader -> loadClass 方法源碼
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded 首先,檢查類是否已經加載
            Class c = findLoadedClass(name);//1
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//2
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);//3
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {//4
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);//5

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

以下對上面代碼的說明

  • 註釋1:檢查傳入的類是否已經加載,如果已經加載就返回該類
  • 註釋2:判斷父加載器是否存在,存在就調用父加載器的loadClass方法,如果不存在就調用註釋3處的findBootstrapClassOrNull 方法,這個方法會直接返回null
  • 如果註解4處代碼成立,說明向上委託流程沒有檢查出類已經被加載,就會執行註釋5處的findClass方法來進行查找流程

下面爲註釋5處的findClass 方法

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

上面的findClass方法中直接拋出異常, 說明需要子類實現,下面爲BaseDexClassLoader的代碼

public class BaseDexClassLoader extends ClassLoader {
    // 需要加載的dex列表
    private final DexPathList pathList;
    // dexPath要加載的dex文件所在的路徑,optimizedDirectory是odex將dexPath
    // 處dex優化後輸出到的路徑,這個路徑必須是手機內部路勁,libraryPath是需要
    // 加載的C/C++庫路徑,parent是父類加載器對象
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

       if (reporter != null) {            
            reportClassLoaderChain();
       }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        // 使用pathList對象查找name類
        Class c = pathList.findClass(name, suppressedExceptions);//1
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}
  • 在註釋 1 的地方調用了 DexPathList 的findClass方法

下面看下DexPathList 中的 findClass 方法

public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {//1
            Class<?> clazz = element.findClass(name, definingContext, suppressed);//2
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
  • 註釋1處遍歷Element 數組dexElements
  • 註釋2處調用Element的findClass方法
  • Element是 DexPathList 的靜態內部類
Element 源碼如下
/*package*/ static class Element {
        /**
         * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
         * (only when dexFile is null).
         */
        private final File path;

        private final DexFile dexFile;

        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

        /**
         * 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).
         */
        public Element(DexFile dexFile, File dexZipPath) {
            this.dexFile = dexFile;
            this.path = dexZipPath;
        }

        public Element(DexFile dexFile) {
            this.dexFile = dexFile;
           this.path = null;
       }

        public Element(File path) {
          this.path = path;
          this.dexFile = null;
        }
    .........
    public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;//1
        }
  • 從Element 的構造方法可以看出,其內部封裝了DexFile,它用於加載dex
  • 在註釋1處如果DexFile不爲null就調用 DexFile 的loadClassBinaryName 方法
DexFile 中 loadClassBinaryName 方法源碼如下
 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 = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);//1
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
  • 在loadClassBinaryName 方法中調用了defineClass方法
  • 註解1處調用了defineClassNative方法來加載dex相關文件,這個方法是Native方法

到此ClassLoader 的查找流程完成,可以概括爲下圖
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章