从底层分析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<IOException> suppressedExceptions = new ArrayList<IOException>();
        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<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        ArrayList<Element> elements = new ArrayList<Element>();
        /*
         * 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<jint>(reinterpret_cast<uintptr_t>(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<const OatFile> 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@bar@baz.jar@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@data@dev.mars.androidclassloadertest@app_output@classes.dex)
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.<init>(DexFile.java:80)
                                                                           at dalvik.system.DexFile.<init>(DexFile.java:59)
                                                                           at dalvik.system.DexPathList.loadDexFile(DexPathList.java:263)
                                                                           at dalvik.system.DexPathList.makeDexElements(DexPathList.java:221)
                                                                           at dalvik.system.DexPathList.<init>(DexPathList.java:112)
                                                                           at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
                                                                           at dalvik.system.PathClassLoader.<init>(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@data@dev.mars.androidclassloadertest@app_output@classes.dex)

直接说明了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文件路径以":"分割合并的字符串。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容