これが両者の最大の違いです。
この記事では、Androidのクラスローディングの仕組みを分析します。AndroidのClassLoaderは、システムClassLoaderとカスタムClassLoaderに分けられます:
- BootClassLoader AndroidはBootClassLoaderを使用して、起動時にいくつかのクラスをプリロードします。クラスローダチェインの先頭にあります。
- アンインストールされた apk ファイルをロードする DexClassLoader。
// /frameworks/base/core/java/android/app/ContextImpl.java
@Override
public ClassLoader getClassLoader() {
 return mPackageInfo != null ?
 mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
// /frameworks/base/core/java/android/app/LoadedApk.java
public ClassLoader getClassLoader() {
 synchronized (this) {
 if (mClassLoader != null) {
 return mClassLoader;
 }
  
 if (mIncludeCode && !mPackageName.equals("android")) {
  
 //PathClassLoaderを作成する
 mClassLoader =
 ApplicationLoaders.getDefault().getClassLoader(
 zip, libraryPath, mBaseClassLoader);
  
 }
 }
}
// /frameworks/base/core/java/android/app/ApplicationLoaders.java
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{
 ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
 synchronized (mLoaders) {
 if (parent == null) {
 parent = baseParent;
 }
 if (parent == baseParent) {
 ClassLoader loader = mLoaders.get(zip);
 if (loader != null) {
 return loader;
 }
  
 //PathClassLoaderを作成する
 PathClassLoader pathClassloader =
 new PathClassLoader(zip, libPath, parent);
 mLoaders.put(zip, pathClassloader);
 return pathClassloader;
 }
 PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
  
 return pathClassloader;
 }
}
上記から、ContextImplはAppがロードするPathClassLoaderをユーザーに提供していることがわかります。
BaseDexClassLoader
// libcore/libdvm/src/main/java/java/lang/ClassLoader.java
public abstract class ClassLoader {
 
 static private class SystemClassLoader {
 //実際、PathClassLoaderは
 public static ClassLoader loader = ClassLoader.createSystemClassLoader(); 
 }
 ...
 /**
 * The parent ClassLoader.
 */
 private ClassLoader parent;
 private static ClassLoader createSystemClassLoader() {
 String classPath = System.getProperty("java.class.path", ".");
 //Androidクラスローダー(PathClassLoader)を作る
 return new PathClassLoader(classPath, BootClassLoader.getInstance());
 }
 public static ClassLoader getSystemClassLoader() {
 return SystemClassLoader.loader;
 }
  
 //VMによってすでにロードされているクラスを返す。すでにロードされている場合は、このクラスを返す。
 protected final Class<?> findLoadedClass(String className) {
 ClassLoader loader;
 if (this == BootClassLoader.getInstance())
 loader = null;
 else
 loader = this;
 return VMClassLoader.findLoadedClass(loader, className);
 }
 //classロードのロジックは、まずロードされたかどうかを判断し、ダイレクト・リターンでロードされたかどうかを判断する。
 protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
 Class<?> clazz = findLoadedClass(className);//クラスがすでにロードされているかどうかを調べる
 
 if (clazz == null) {//clazznull値は、dexがロードされていないことを意味する。
 try {
 clazz = parent.loadClass(className, false);//dexはまずparent経由でロードされ、これは2親委譲モデルに従っている。
 } catch (ClassNotFoundException e) {
 // Don't want to see this.
 }
 if (clazz == null) {//parent loaderロードに失敗した場合は、findClass経由でロードする。
 clazz = findClass(className);//このクラスでは、findClassはnullとして実装されている。
 }
 }
 return clazz;//ロードされたクラスを返す
 }
 protected Class<?> findClass(String className) throws ClassNotFoundException {
 throw new ClassNotFoundException(className);
 }
}
クラスのロードの基本的なロジックの実装のためのClassLoaderを見ることができる、すべての最初のそれはfindLoadedClassを通じて、それがロードされているかどうかをロードするクラスを見つけるために、それが直接リターンにロードされている場合、それ以外の場合は、2つの親デリゲーションのロードモデルに沿ったものです親ローダを介してロードされ、親クラスローダは、クラスを見つけてロードする場合、それ以外の場合は、子クラスを介してロードされ、子クラスを介してロードされ、findClassを介してロードされます。それ以外の場合は、子クラスのローダを介してロードされ、それはfindClassを介してロードされます。ですから、findClass特有のロジックを実装する必要があります。そして、BaseDexClassLoaderでは、findClassクラスのロードロジックに焦点を当てる必要があります。
//libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public class BaseDexClassLoader extends ClassLoader {
 private final DexPathList pathList;
 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
 String libraryPath, ClassLoader parent) {
 super(parent);
 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
 }
 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
 //BaseDexClassLoaderClassLoaderから継承されたDexPathListは、Classを見つけるタスクをDexPathListに委譲している。
 Class c = pathList.findClass(name, suppressedExceptions);
 if (c == null) {//見つからない場合は例外を投げる
 ClassNotFoundExceptionfe = new ClassNotFoundException("Didn't find class "" + name + "" on path: " + pathList);
 for (Throwable t : suppressedExceptions) {
fe.addSuppressed(t);
 }
 throwfe;
 }
 return c;
 }
  
}
BaseDexClassLoaderの実装は非常にシンプルで、コンストラクタのメソッドで初期化されるDexPathListの内部メンバpathListを持ち、クラスファイルとリソースファイルを含むjar/apkファイルのリストを表します。findClassでは、実際にクラスをロードするタスクをpathListに委譲します。その後、DexPathListを再度分析する必要があります。
classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.yujian.myapplication-2.apk"],nativeLibraryDirectories=[/data/app-lib/com.yujian.myapplication-2, /system/lib]]]
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
 ...
 private final Element[] dexElements;//Elementはdex/apkファイルまたはディレクトリに対応する。
  
 private final File[] nativeLibraryDirectories;
 //ここでdexPathは複数のdexファイルを含むことができるため、DexPathListと呼ばれている。
 public DexPathList(ClassLoader definingContext, String dexPath,
 String libraryPath, File optimizedDirectory) {
  
 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
 suppressedExceptions);//要素配列を生成する
  
 this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
 }
}
 static class Element {
 private final File file;//ソースファイルを表す
 private final boolean isDirectory;//現在記述されているファイルがディレクトリかどうか
 private final File zip;//ソースファイルが非圧縮の場合、zipとfileは同じファイルである。
 private final DexFile dexFile;//dex 
  
 public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
 this.file = file;
 this.isDirectory = isDirectory;
 this.zip = zip;
 this.dexFile = dexFile;
 }
  
}
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
 ArrayList<IOException> suppressedExceptions) {
 ArrayList<Element> elements = new ArrayList<Element>();
 for (File file : files) {//ファイルを反復処理する
 File zip = null;
 DexFile dex = null;
 String name = file.getName();// 
 if (name.endsWith(DEX_SUFFIX)) {// .dex
 // Raw dex file (not inside a zip/jar).
 try {
 dex = loadDexFile(file, optimizedDirectory);//loadDexFileを使って直接ロードする
 } catch (IOException ex) {
 System.logE("Unable to load dex file: " + file, ex);
 }
 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
 || name.endsWith(ZIP_SUFFIX)) {// .apk/.jar/.zip 
 zip = file;//zipに割り当てる
 try {
 dex = loadDexFile(file, optimizedDirectory);
 } catch (IOException suppressed) {
 /*
 * IOException might get thrown "legitimately" by the DexFile constructor if the
 * zip file turns out to be resource-only (that is, no classes.dex file in it).
 * Let dex == null and hang on to the exception to add to the tea-leaves for
 * when findClass returns null.
 */
 suppressedExceptions.add(suppressed);
 }
 } else if (file.isDirectory()) {//ディレクトリの場合
 // We support directories for looking up resources.
 // This is only useful for running libcore tests.
 elements.add(new Element(file, true, null, null));//要素ディレクトリを追加する trueはディレクトリを意味する
 } else {
 System.logW("Unknown file type for: " + file);
 }
 if ((zip != null) || (dex != null)) {
 elements.add(new Element(file, false, zip, dex));//エレメントにdexファイルを追加する
 }
 }
 return elements.toArray(new Element[elements.size()]);
}
makeDexElements は、dexPath で表される dex/apk ファイルまたはディレクトリから要素の配列を生成します。 DexPathList を構築する際に渡される dexPath には複数のファイルパスが含まれる場合があり、上記で出力される情報は 1 つの apk についてのものです。splitDexPath は、dexPath で表されるファイルのリストを表す ArrayList を返します。このファイルリストはループを通して処理され、dex/apk/jar/zip のような異なるファイルタイプやディレクトリは異なる方法で処理されます:
- dexファイルであれば、loadDexFileで読み込み、そのファイルを記述したDexFileを返します。
- zip/apk/jarの場合、まず圧縮ファイルをzipに保存し、次にloadDexFileして、記述するdexファイルのDexFileを返します。
- ディレクトリの場合は、そのエレメントを生成して elements 配列に追加します。
1 と 2 のどちらの場合も、最終的に Element が作成され、elements 配列に追加されます。
private static DexFile loadDexFile(File file, File optimizedDirectory)
 throws IOException {
 if (optimizedDirectory == null) {//PathClassLoaderのoptimisedDirectoryは常にNULLである。
 return new DexFile(file);//DexFileを直接 newすると、次のようになる。
 } else {
 String optimizedPath = optimizedPathFor(file, optimizedDirectory);
 return DexFile.loadDex(file.getPath(), optimizedPath, 0);
 }
}
loadDexFileは実際にはファイル用のDexFileオブジェクトを作るだけです。名前からすると、Dexファイルに特化しているように見えます。
DexPathListがクラスを読み込む方法は以下の通りです。
public Class findClass(String name, List<Throwable> suppressed) {
 for (Element element : dexElements) {//dexElementsを繰り返し処理する
 DexFile dex = element.dexFile;
 if (dex != null) {//dexFile経由でクラスをロードする
 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
 if (clazz != null) {
 return clazz;//ロードに成功
 }
 }
 }
 if (dexElementsSuppressedExceptions != null) {
 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
 }
 return null;
}
DexPathList はまず dexElements を走査し、DexFile に対して ClassBinaryName をロードして読み込み、見つかればそれを返します。ここで再びDexFileにロードに行くので、DexFileがどのようにロードされるかを確認する必要があります。
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
 return defineClass(name, loader, mCookie, suppressed);
}
//DexFile 
private static Class defineClass(String name, ClassLoader loader, int cookie,
 List<Throwable> suppressed) {
 Class result = null;
 try {
 //ネイティブ・ランタイム/native/dalvik経由でロードする_system_DexFile.cc
 result = defineClassNative(name, loader, cookie);
 } catch (NoClassDefFoundError e) {
 if (suppressed != null) {
 suppressed.add(e);
 }
 } catch (ClassNotFoundException e) {
 if (suppressed != null) {
 suppressed.add(e);
 }
 }
 return result;
}
DexFileはクラスをロードするためにdefineClassNativeメソッドを呼び出しますが、この名前からわかるように、実際にはネイティブレイヤーからクラスをロードします。これで、DexPathListのクラスをロードするロジックの分析は終了です。
これは、dexElements 配列の先頭にある dex ファイルが最初にアクセスされ、修復されたクラスが最初にロードされるためです。
PathClassLoader
PathClassLoaderはBaseDexClassLoaderのサブクラスであり、そのクラスロード機能は親クラスに依存しています。その実装を見てみましょう
public class PathClassLoader extends BaseDexClassLoader {
 public PathClassLoader(String dexPath, ClassLoader parent) {
 super(dexPath, null, null, parent);
 }
 public PathClassLoader(String dexPath, String libraryPath,
 ClassLoader parent) {
 super(dexPath, null, libraryPath, parent);
 }
}
PathClassLoaderの実装は非常にシンプルで、2つの異なるコンストラクタ・メソッドを提供するだけです。これら2つのコンストラクタ・メソッドの違いは、libパスを提供するかどうかで、デフォルトのパスはsystem/lib/とdata/app-lib/pakage-nameです。
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.
 * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getDir(String, int)} to create
 * such a directory: <pre> {@code
 * File dexOutputDir = context.getDir("dex", 0);
 * }</pre>
 *
 * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */
public class DexClassLoader extends BaseDexClassLoader {
 
 public DexClassLoader(String dexPath, String optimizedDirectory,
 String libraryPath, ClassLoader parent) {
 super(dexPath, new File(optimizedDirectory), libraryPath, parent);
 }
}





