从底层分析PathClassLoader和DexClassLoader的区别,基于Android4.4

Android虚拟机的类加载机制

Hotspot虚拟机中由ClassLoader完成类的加载。而Android虚拟机不能加载.class字节码文件,.dex才是Android虚拟机能够识别并加载的文件。Android虚拟机使用PathClassLoader和DexClassLoader两种加载器。

PathClassLoader和DexClassLoader的区别

通常我们知道PathClassLoader只能加载已安装的应用,而DexClassLoader支持加载本地的apk、zip、jar、dex,下面从源码分析两者区别。

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);
    }
}

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

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);
    }
    ...
}

由两者的构造方法可以看出,PathClassLoader相比DexClassLoader传给父类BaseDexClassLoader 的optimizedDirectory参数为NULL。
具体在DexPathList中有什么影响呢:

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }

        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }

        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }

            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }

        this.definingContext = definingContext;
        ArrayList suppressedExceptions = new ArrayList();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

这里注意makeDexElements函数第一个参数splitDexPath(dexPath) , splitDexPath函数将dexPath字符串以":"分割为多个路径,也就是PathClassLoader和DexClassLoader都支持在构造方法中传入以":"分割多文件路径的参数。简而言之,支持多个文件的加载。
再细看makeDexElements函数:

 private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,
                                             ArrayList suppressedExceptions) {
        ArrayList elements = new ArrayList();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();

            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } 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)) {
                zip = file;

                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    suppressedExceptions.add(suppressed);
                }
            } else if (file.isDirectory()) {
                elements.add(new Element(file, true, null, null));
            } else {
                System.logW("Unknown file type for: " + file);
            }

            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }

        return elements.toArray(new Element[elements.size()]);
    }

再看loadDexFile函数:

private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }

由PathClassLoader传入的optimizedDirectory为空,因此执行 new DexFile(file):

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");
        //System.out.println("DEX FILE cookie is " + mCookie);
    }

到此最后会执行openDexFile(fileName, null, 0)。

由于DexClassLoader通常传入一个开发者指定的optimizedDirectory,如果传入为null则跟PathClassLoader的构造无差别,因此看DexFile.loadDex(file.getPath(), optimizedPath, 0)函数:

 static public DexFile loadDex(String sourcePathName, String outputPathName,
        int flags) throws IOException {

        /*
         * TODO: we may want to cache previously-opened DexFile objects.
         * The cache would be synchronized with close().  This would help
         * us avoid mapping the same DEX more than once when an app
         * decided to open it multiple times.  In practice this may not
         * be a real issue.
         */
        return new DexFile(sourcePathName, outputPathName, flags);
    }

private DexFile(String sourceName, String outputName, int flags) throws IOException {
        if (outputName != null) {
            try {
                String parent = new File(outputName).getParent();
                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                    throw new IllegalArgumentException("Optimized data directory " + parent
                            + " is not owned by the current user. Shared storage cannot protect"
                            + " your application from code injection attacks.");
                }
            } catch (ErrnoException ignored) {
                // assume we'll fail with a more contextual error later
            }
        }

        mCookie = openDexFile(sourceName, outputName, flags);
        mFileName = sourceName;
        guard.open("close");
        //System.out.println("DEX FILE cookie is " + mCookie);
    }

DexClassLoader最后也是执行openDexFile(sourceName, outputName, flags)。

再看openDexFile函数:

private static int openDexFile(String sourceName, String outputName,
        int flags) throws IOException {
        return openDexFileNative(new File(sourceName).getCanonicalPath(),
                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
                                 flags);
    }
native private static int openDexFileNative(String sourceName, String outputName,
        int flags) throws IOException;

到这一步可以知道DexClassLoader和PathClassLoader的构造最后都会执行到openDexFileNative这个Native函数,所不同的是PathClassLoader传入outputName的必为NULL。

下面以http://androidxref.com/4.4_r1/xref/art/runtime/native/dalvik_system_DexFile.cc的源码继续分析:

static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == NULL) {
    return 0;
  }
  std::string dex_location(sourceName.c_str());
  NullableScopedUtfChars outputName(env, javaOutputName);
  if (env->ExceptionCheck()) {
    return 0;
  }
  ScopedObjectAccess soa(env);

  uint32_t dex_location_checksum;
  if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
    LOG(WARNING) << "Failed to compute checksum: " << dex_location;
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                   "Unable to get checksum of dex file: %s", dex_location.c_str());
    return 0;
  }

  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  const DexFile* dex_file;
  if (outputName.c_str() == NULL) {
    dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
  } else {
    std::string oat_location(outputName.c_str());
    dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
  }
  if (dex_file == NULL) {
    LOG(WARNING) << "Failed to open dex file: " << dex_location;
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                   "Unable to open dex file: %s", dex_location.c_str());
    return 0;
  }
  return static_cast(reinterpret_cast(dex_file));
}

注意 if (outputName.c_str() == NULL)这个判断,我们知道PathClassLoader传入的javaOutputName一定为NULL。因此会执行FindDexFileInOatFileFromDexLocation函数,依然以Android4.4为例,函数定义在http://androidxref.com/4.4_r1/xref/art/runtime/class_linker.cc:

const DexFile* ClassLinker::FindDexFileInOatFileFromDexLocation(const std::string& dex_location,
                                                                uint32_t dex_location_checksum) {
  WriterMutexLock mu(Thread::Current(), dex_lock_);

  const OatFile* open_oat_file = FindOpenedOatFileFromDexLocation(dex_location,
                                                                  dex_location_checksum);
  if (open_oat_file != NULL) {
    return open_oat_file->GetOatDexFile(dex_location, &dex_location_checksum)->OpenDexFile();
  }

  // Look for an existing file next to dex. for example, for
  // /foo/bar/baz.jar, look for /foo/bar/baz.odex.
  std::string odex_filename(OatFile::DexFilenameToOdexFilename(dex_location));
  UniquePtr oat_file(FindOatFileFromOatLocationLocked(odex_filename));
  if (oat_file.get() != NULL) {
    uint32_t dex_location_checksum;
    if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
      // If no classes.dex found in dex_location, it has been stripped, assume oat is up-to-date.
      // This is the common case in user builds for jar's and apk's in the /system directory.
      const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, NULL);
      CHECK(oat_dex_file != NULL) << odex_filename << " " << dex_location;
      RegisterOatFileLocked(*oat_file);
      return oat_dex_file->OpenDexFile();
    }
    const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file.release(),
                                                              dex_location,
                                                              dex_location_checksum);
    if (dex_file != NULL) {
      return dex_file;
    }
  }
  // Look for an existing file in the dalvik-cache, validating the result if found
  // not found in /foo/bar/baz.odex? try /data/dalvik-cache/foo@[email protected]@classes.dex
  std::string cache_location(GetDalvikCacheFilenameOrDie(dex_location));
  oat_file.reset(FindOatFileFromOatLocationLocked(cache_location));
  if (oat_file.get() != NULL) {
    uint32_t dex_location_checksum;
    if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
      LOG(WARNING) << "Failed to compute checksum: " << dex_location;
      return NULL;
    }
    const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(oat_file.release(),
                                                              dex_location,
                                                              dex_location_checksum);
    if (dex_file != NULL) {
      return dex_file;
    }
    if (TEMP_FAILURE_RETRY(unlink(cache_location.c_str())) != 0) {
      PLOG(FATAL) << "Failed to remove obsolete oat file from " << cache_location;
    }
  }
  LOG(INFO) << "Failed to open oat file from " << odex_filename << " or " << cache_location << ".";

  // Try to generate oat file if it wasn't found or was obsolete.
  std::string oat_cache_filename(GetDalvikCacheFilenameOrDie(dex_location));
  return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_cache_filename);
}

从函数的命名我们可以得知函数是用来寻找生成的.oat文件的,.oat文件在ART虚拟机下安装时就会生成,对于未安装的APK,是不会生成这个文件的。再看后续执行的FindOrCreateOatFileForDexLocation函数,由PathClassLoader传入的 oat_cache_filename对于未安装的APK是空指针:

const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,
                                                                    uint32_t dex_location_checksum,
                                                                    const std::string& oat_location) {
  // We play a locking game here so that if two different processes
  // race to generate (or worse, one tries to open a partial generated
  // file) we will be okay. This is actually common with apps that use
  // DexClassLoader to work around the dex method reference limit and
  // that have a background service running in a separate process.
  ScopedFlock scoped_flock;
  if (!scoped_flock.Init(oat_location)) {
    LOG(ERROR) << "Failed to open locked oat file: " << oat_location;
    return NULL;
  }

  // Check if we already have an up-to-date output file
  const DexFile* dex_file = FindDexFileInOatLocation(dex_location,
                                                     dex_location_checksum,
                                                     oat_location);
  if (dex_file != NULL) {
    return dex_file;
  }

  // Generate the output oat file for the dex file
  VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
  if (!GenerateOatFile(dex_location, scoped_flock.GetFile().Fd(), oat_location)) {
    LOG(ERROR) << "Failed to generate oat file: " << oat_location;
    return NULL;
  }
  const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,
                                          !Runtime::Current()->IsCompiler());
  if (oat_file == NULL) {
    LOG(ERROR) << "Failed to open generated oat file: " << oat_location;
    return NULL;
  }
  RegisterOatFileLocked(*oat_file);
  const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
  if (oat_dex_file == NULL) {
    LOG(ERROR) << "Failed to find dex file " << dex_location
               << " (checksum " << dex_location_checksum
               << ") in generated oat file: " << oat_location;
    return NULL;
  }
  const DexFile* result = oat_dex_file->OpenDexFile();
  CHECK_EQ(dex_location_checksum, result->GetLocationChecksum())
          << "dex_location=" << dex_location << " oat_location=" << oat_location << std::hex
          << " dex_location_checksum=" << dex_location_checksum
          << " DexFile::GetLocationChecksum()=" << result->GetLocationChecksum();
  return result;
}

因为oat_location未NULL,所以会返回NULL,在回到openDexFileNative:

if (dex_file == NULL) {
    LOG(WARNING) << "Failed to open dex file: " << dex_location;
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                   "Unable to open dex file: %s", dex_location.c_str());
    return 0;
  }

最终会返回失败,并抛出异常。

而DexClassLoader因为指定的输出文件目录不为空,虚拟机可以生成优化的OAT文件。当然如果传入非法的目录一样是会失败的。

测试PathClassLoader与DexClassLoader

从上文我们知道PathClassLoader不能加载非已安装的apk文件,而Dex则可以动态加载类并输出到指定优化路径。
测试思路,将一个dex文件放在assets下,在apk启动后由两种类加载器加载dex。

首先创建一个module,并在其中定义类A:

package dev.mars.app2;
public class A {
    public static void printName(){
        Log.e("dev_mars",A.class.getName()+" printName()");
    }
}

由AS build APK生成apk,从中取出classes.dex,放到主module下的assets文件夹下。注意这里我去除了supportv4、v7等不需要的包。

在主module下创建自定义Application:

package dev.mars.androidclassloadertest;

import android.app.Application;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

public class MyApplication extends Application{
    private static final String LOG_TAG = "dev_mars";

    private static void LOGE(String str){
        Log.e(LOG_TAG,str);
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        String dexFilePath = getDexFilePath();
        LOGE("dexFilePath : "+dexFilePath);
        String optimziedFolderPath = getDir("optimized_dex",MODE_PRIVATE).getAbsolutePath();
        LOGE("optimziedFolderPath : "+optimziedFolderPath);
        if(dexFilePath!=null) {
            loadDexFileByPathClassLoader(dexFilePath);
            //loadDexFileByDexClassLoader(dexFilePath,optimziedFolderPath);
        }
    }

    private void loadDexFileByDexClassLoader(String dexFilePath, String optimziedFolderPath) {
        DexClassLoader dexClassLoader = new DexClassLoader(dexFilePath,optimziedFolderPath,null,getClassLoader());

        try {
            Class A = dexClassLoader.loadClass(" dev.mars.app2.A");
            executeMethod(A);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private void executeMethod(Class A) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
        Object a = A.newInstance();
        Method printName = A.getDeclaredMethod("printName");
        printName.invoke(a);
        LOGE("loadDexFileByPathClassLoader finish ");
    }

    private void loadDexFileByPathClassLoader(String dexFilePath) {
        LOGE("loadDexFileByPathClassLoader : "+dexFilePath);
        PathClassLoader pathClassLoader = new PathClassLoader(dexFilePath,null,getClassLoader());
        try {
            Class A = pathClassLoader.loadClass(" dev.mars.app2.A");
            executeMethod(A);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    private String getDexFilePath(){
        String fileName = "classes.dex";
        String dexFilePath = null;
        AssetManager assetManager = getAssets();
        try {
            InputStream is = assetManager.open(fileName);
            File outputFolderFile = getDir("output",MODE_PRIVATE);

            dexFilePath = outputFolderFile.getAbsolutePath()+"/"+fileName;
            FileOutputStream fs = new FileOutputStream(dexFilePath);
            byte[] buffer =new byte[2048];
            int readSize =0;
            while (readSize!=-1){
                readSize = is.read(buffer);
                if(readSize>0){
                    fs.write(buffer,0,readSize);
                }
            }
            fs.flush();
            fs.close();
            is.close();
            Log.e("dev_mars","dexFilePath : "+dexFilePath);
            return dexFilePath;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

由loadDexFileByPathClassLoader函数的执行结果log来看:

05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/dev_mars: loadDexFileByPathClassLoader : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/dalvikvm: Dex cache directory isn't writable: /data/dalvik-cache
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest I/dalvikvm: Unable to open or create cache for /data/data/dev.mars.androidclassloadertest/app_output/classes.dex (/data/dalvik-cache/data@[email protected]@[email protected])
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest I/dalvikvm: Zip is good, but no classes.dex inside, and no valid .odex file in the same directory
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/System: Unable to load dex file: /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:24:01.130 5006-5006/dev.mars.androidclassloadertest E/System: java.io.IOException: unable to open DEX file
                                                                           at dalvik.system.DexFile.openDexFileNative(Native Method)
                                                                           at dalvik.system.DexFile.openDexFile(DexFile.java:296)
                                                                           at dalvik.system.DexFile.(DexFile.java:80)
                                                                           at dalvik.system.DexFile.(DexFile.java:59)
                                                                           at dalvik.system.DexPathList.loadDexFile(DexPathList.java:263)
                                                                           at dalvik.system.DexPathList.makeDexElements(DexPathList.java:221)
                                                                           at dalvik.system.DexPathList.(DexPathList.java:112)
                                                                           at dalvik.system.BaseDexClassLoader.(BaseDexClassLoader.java:48)
                                                                           at dalvik.system.PathClassLoader.(PathClassLoader.java:65)
                                                                           at dev.mars.androidclassloadertest.MyApplication.loadDexFileByPathClassLoader(MyApplication.java:42)
                                                                           at dev.mars.androidclassloadertest.MyApplication.attachBaseContext(MyApplication.java:36)
                                                                           at java.lang.reflect.Method.invokeNative(Native Method)
                                                                           at java.lang.reflect.Method.invoke(Method.java:515)
                                                                           at com.android.tools.fd.runtime.BootstrapApplication.attachBaseContext(BootstrapApplication.java:251)
                                                                           at android.app.Application.attach(Application.java:181)
                                                                           at android.app.Instrumentation.newApplication(Instrumentation.java:991)
                                                                           at android.app.Instrumentation.newApplication(Instrumentation.java:975)
                                                                           at android.app.LoadedApk.makeApplication(LoadedApk.java:511)
                                                                           at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4317)
                                                                           at android.app.ActivityThread.access$1500(ActivityThread.java:135)
                                                                           at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                           at android.os.Looper.loop(Looper.java:136)
                                                                           at android.app.ActivityThread.main(ActivityThread.java:5017)
                                                                           at java.lang.reflect.Method.invokeNative(Native Method)
                                                                           at java.lang.reflect.Method.invoke(Method.java:515)
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
                                                                           at dalvik.system.NativeStart.main(Native Method)

其中:

Dex cache directory isn't writable: /data/dalvik-cache
Unable to open or create cache for /data/data/dev.mars.androidclassloadertest/app_output/classes.dex (/data/dalvik-cache/data@[email protected]@[email protected])

直接说明了PathClassLoader加载dex失败的原因,因为目标未被安装找不到缓存的的执行文件,/data/dalvik-cache又不能被写入,所以抛出异常。因此PathClassLoader只能用于加载已安装的APK。

再来看执行loadDexFileByDexClassLoader函数的log:

05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dexFilePath : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dexFilePath : /data/data/dev.mars.androidclassloadertest/app_output/classes.dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest E/dev_mars: optimziedFolderPath : /data/data/dev.mars.androidclassloadertest/app_optimized_dex
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest I/dalvikvm: DexOpt: source file mod time mismatch (591e76a5 vs 591e7757)
05-19 00:40:55.510 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: ODEX file is stale or bad; removing and retrying (/data/data/dev.mars.androidclassloadertest/app_optimized_dex/classes.dex)
05-19 00:40:55.520 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DexOpt: --- BEGIN 'classes.dex' (bootstrap=0) ---
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DexOpt: --- END 'classes.dex' (success) ---
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest D/dalvikvm: DEX prep '/data/data/dev.mars.androidclassloadertest/app_output/classes.dex': copy in 0ms, rewrite 53ms
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest E/dev_mars: dev.mars.app2.A printName()
05-19 00:40:55.580 19851-19851/dev.mars.androidclassloadertest E/dev_mars: loadDexFileByPathClassLoader finish 

可以看到dex被成功加载并用反射技术成功创建了A的实例a,并调用printName方法打印了log。遍历下optimized文件夹看看多了什么:

05-19 00:43:08.390 21786-21786/dev.mars.androidclassloadertest E/dev_mars: /data/data/dev.mars.androidclassloadertest/app_optimized_dex/classes.dex

可以看到使用DexClassLoader加载dex生成了优化的dex,猜想等到第二次加载该dex就不需要再次生成优化的dex,直接加载优化的dex从而提高执行速度。

总结

  • PathClassLoader和DexClassLoader都是Android虚拟机支持的类加载器,所不同的是PathClassLoader只能加载已安装的APK(在Native层如果发现加载的是非已安装的APK会抛出异常),并且作为APP默认的类加载器。
  • DexClassLoader可以加载dex、apk、jar、zip等格式的插件,这些插件不需要已安装。
  • 传入的dexPath既可以是单个dex文件的路径,也可以是多个dex文件路径以":"分割合并的字符串。

你可能感兴趣的:(从底层分析PathClassLoader和DexClassLoader的区别,基于Android4.4)