從底層分析PathClassLoader和DexClassLoader的區(qū)別,基于Android4.4

Android虛擬機(jī)的類加載機(jī)制

Hotspot虛擬機(jī)中由ClassLoader完成類的加載。而Android虛擬機(jī)不能加載.class字節(jié)碼文件臊岸,.dex才是Android虛擬機(jī)能夠識(shí)別并加載的文件伞芹。Android虛擬機(jī)使用PathClassLoader和DexClassLoader兩種加載器凳谦。

PathClassLoader和DexClassLoader的區(qū)別

通常我們知道PathClassLoader只能加載已安裝的應(yīng)用,而DexClassLoader支持加載本地的apk挎扰、zip囚霸、jar腰根、dex,下面從源碼分析兩者區(qū)別邮辽。

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

由兩者的構(gòu)造方法可以看出唠雕,PathClassLoader相比DexClassLoader傳給父類BaseDexClassLoader 的optimizedDirectory參數(shù)為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函數(shù)第一個(gè)參數(shù)splitDexPath(dexPath) , splitDexPath函數(shù)將dexPath字符串以":"分割為多個(gè)路徑吨述,也就是PathClassLoader和DexClassLoader都支持在構(gòu)造方法中傳入以":"分割多文件路徑的參數(shù)岩睁。簡(jiǎn)而言之,支持多個(gè)文件的加載揣云。
再細(xì)看makeDexElements函數(shù):

 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函數(shù):

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為空捕儒,因此執(zhí)行 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);
    }

到此最后會(huì)執(zhí)行openDexFile(fileName, null, 0)。

由于DexClassLoader通常傳入一個(gè)開發(fā)者指定的optimizedDirectory,如果傳入為null則跟PathClassLoader的構(gòu)造無(wú)差別,因此看DexFile.loadDex(file.getPath(), optimizedPath, 0)函數(shù):

 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最后也是執(zhí)行openDexFile(sourceName, outputName, flags)邓夕。

再看openDexFile函數(shù):

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的構(gòu)造最后都會(huì)執(zhí)行到openDexFileNative這個(gè)Native函數(shù)刘莹,所不同的是PathClassLoader傳入outputName的必為NULL。

下面以http://androidxref.com/4.4_r1/xref/art/runtime/native/dalvik_system_DexFile.cc的源碼繼續(xù)分析:

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)這個(gè)判斷焚刚,我們知道PathClassLoader傳入的javaOutputName一定為NULL点弯。因此會(huì)執(zhí)行FindDexFileInOatFileFromDexLocation函數(shù),依然以Android4.4為例,函數(shù)定義在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);
}

從函數(shù)的命名我們可以得知函數(shù)是用來(lái)尋找生成的.oat文件的矿咕,.oat文件在ART虛擬機(jī)下安裝時(shí)就會(huì)生成抢肛,對(duì)于未安裝的APK,是不會(huì)生成這個(gè)文件的碳柱。再看后續(xù)執(zhí)行的FindOrCreateOatFileForDexLocation函數(shù),由PathClassLoader傳入的 oat_cache_filename對(duì)于未安裝的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;
}

因?yàn)閛at_location未NULL捡絮,所以會(huì)返回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;
  }

最終會(huì)返回失敗莲镣,并拋出異常福稳。

而DexClassLoader因?yàn)橹付ǖ妮敵鑫募夸洸粸榭眨摂M機(jī)可以生成優(yōu)化的OAT文件瑞侮。當(dāng)然如果傳入非法的目錄一樣是會(huì)失敗的的圆。

測(cè)試PathClassLoader與DexClassLoader

從上文我們知道PathClassLoader不能加載非已安裝的apk文件鼓拧,而Dex則可以動(dòng)態(tài)加載類并輸出到指定優(yōu)化路徑。
測(cè)試思路略板,將一個(gè)dex文件放在assets下毁枯,在apk啟動(dòng)后由兩種類加載器加載dex。

首先創(chuàng)建一個(gè)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下創(chuàng)建自定義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函數(shù)的執(zhí)行結(jié)果log來(lái)看:

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)

直接說(shuō)明了PathClassLoader加載dex失敗的原因,因?yàn)槟繕?biāo)未被安裝找不到緩存的的執(zhí)行文件娱节,/data/dalvik-cache又不能被寫入挠蛉,所以拋出異常。因此PathClassLoader只能用于加載已安裝的APK肄满。

再來(lái)看執(zhí)行l(wèi)oadDexFileByDexClassLoader函數(shù)的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被成功加載并用反射技術(shù)成功創(chuàng)建了A的實(shí)例a谴古,并調(diào)用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生成了優(yōu)化的dex稠歉,猜想等到第二次加載該dex就不需要再次生成優(yōu)化的dex掰担,直接加載優(yōu)化的dex從而提高執(zhí)行速度。

總結(jié)

  • PathClassLoader和DexClassLoader都是Android虛擬機(jī)支持的類加載器怒炸,所不同的是PathClassLoader只能加載已安裝的APK(在Native層如果發(fā)現(xiàn)加載的是非已安裝的APK會(huì)拋出異常)带饱,并且作為APP默認(rèn)的類加載器。
  • DexClassLoader可以加載dex阅羹、apk勺疼、jar、zip等格式的插件捏鱼,這些插件不需要已安裝执庐。
  • 傳入的dexPath既可以是單個(gè)dex文件的路徑,也可以是多個(gè)dex文件路徑以":"分割合并的字符串导梆。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轨淌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子问潭,更是在濱河造成了極大的恐慌猿诸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狡忙,死亡現(xiàn)場(chǎng)離奇詭異梳虽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)灾茁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門窜觉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谷炸,“玉大人,你說(shuō)我怎么就攤上這事禀挫⊙福” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵语婴,是天一觀的道長(zhǎng)描孟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)砰左,這世上最難降的妖魔是什么匿醒? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮缠导,結(jié)果婚禮上廉羔,老公的妹妹穿的比我還像新娘。我一直安慰自己僻造,他們只是感情好憋他,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著髓削,像睡著了一般竹挡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔬螟,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天此迅,我揣著相機(jī)與錄音,去河邊找鬼旧巾。 笑死耸序,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲁猩。 我是一名探鬼主播坎怪,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼廓握!你這毒婦竟也來(lái)了搅窿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤隙券,失蹤者是張志新(化名)和其女友劉穎男应,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娱仔,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沐飘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耐朴。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡借卧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筛峭,到底是詐尸還是另有隱情铐刘,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布影晓,位于F島的核電站镰吵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挂签。R本人自食惡果不足惜捡遍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竹握。 院中可真熱鬧,春花似錦辆飘、人聲如沸啦辐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芹关。三九已至,卻和暖如春紧卒,著一層夾襖步出監(jiān)牢的瞬間侥衬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工跑芳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轴总,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓博个,卻偏偏與公主長(zhǎng)得像怀樟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盆佣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容