美文网首页Android相关Android知识Android开发
从底层分析PathClassLoader和DexClassLoa

从底层分析PathClassLoader和DexClassLoa

作者: Mars_M | 来源:发表于2017-05-19 12:50 被阅读266次

    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文件路径以":"分割合并的字符串。

    相关文章

      网友评论

      • 渡过:有问题,4.4的Android系统还是使用davlik虚拟机的,但是由于4.4引入了art虚拟机作为测试,源码里面也有art的源码,你分析的是art的情况。你可以从输出的日志来反推。4.4上Native
        的DexFile源码应该是这样的。http://androidxref.com/4.4.2_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp
        Mars_M:多谢指出!
      • 四单老师:找了半天,终于找到了介绍的这么详细的文章,主要还有剖析了native的代码。因为c++不太懂,所以问下,最后产生差异的地方是不是在native中因为传入的opt_out_path为空导致走入FindDexFileInOatFileFromDexLocation这个分支,然后在这个分支中只会在安装过的apk中产生对应的oat,不然会失败返回空。但是如果传入的opt_out_path不为空,就会走FindOrCreateOatFileForDexLocation,根据合法的路径产生优化后的dex完成加载。是不是这个意思啊?:smile:
        Mars_M:根据我的分析,是这样的,只有在native层才能看出两者的差异

      本文标题:从底层分析PathClassLoader和DexClassLoa

      本文链接:https://www.haomeiwen.com/subject/chzexxtx.html