DexClassLoader与PathClassLoader区别

在Android中想要热更新和插件化功能,是绕不开类加载器的。插件包中会有dex包和资源,通过阅读源码可知,DexClassLoader和PathClassLoader可以加载dex,AssetManager加载apk中的资源。这里有个疑问DexClassLoader和PathClassLoader都可以加载dex到底什么区别呢?为什么目前主流插件化方案都是使用PathClassLoader加载宿主dex,使用PathClassLoader加载插件的dex?可以替换使用吗?接下来看下源码

首先看一下DexClassLoader源码

public class DexClassLoader extends BaseDexClassLoader {
    /**
     * @param dexPath 包含dex的Apk或者jar路径
     * @param optimizedDirectory 优化文件目录,不能为空(dalvik中odex文件目录,art中oat文件目录)
     * @param librarySearchPath native库目录,可以为null
     * @param parent 父加载器
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

以下是PathClassLoader源码

public class PathClassLoader extends BaseDexClassLoader {
   
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }


    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

由DexClassLoader与PathClassLoader可知,都是继承BaseDexClassLoader,PathClassLoader优化文件目录为null,而DexClassLoader必须不能为null,其他功能都相同。在这边有个疑问如果优化目录设置为null,那么生成的优化文件放在哪里,接下来看一下BaseDexClassLoader源码

 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        //创建DexPathList对象
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }

BaseDexClassLoader构造函数中会创建DexPathList对象,接下来看一下DexPathList构造函数源码

 public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
    //省略 optimizedDirectory判断       
      ...
     
        this.definingContext = definingContext;
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

由以上源码可知,会调用makeDexElements方法

 private static Element[] makeDexElements(ArrayList files,
            File optimizedDirectory) {
        ArrayList elements = new ArrayList();

        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();

            if (name.endsWith(DEX_SUFFIX)) {
            
                try {
                //加载dex文件
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
             
                }
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                zip = file;
                //加载其他包含dex的压缩文件
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ignored) {
                 
                }
            }
            ...
            //把DexFile对象放到elements中
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }

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

makeDexElements会调用loadDexFile用来加载dex或是包含dex的Apk、Jar、zip压缩文件,返回的DexFile对象放到elements数组中

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

loadDexFile方法中会判断optimizedDirectory是否为null,如果传入的optimizedDirectory为null会直接创建DexFile对象,接下来看一下DexFile构造函数

    public DexFile(File file) throws IOException {
    //调用一个String参数的构造函数
        this(file.getPath());
    }
    public DexFile(String fileName) throws IOException {
        this(fileName, null, null);
    }
    
    public  DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        mCookie = openDexFile(fileName, null, 0, loader, elements);
        ...
    }
    

最终会调用openDexFile

    private static Object openDexFile(String sourceName, String outputName, int flags,
            ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                 (outputName == null)
                                     ? null
                                     : new File(outputName).getAbsolutePath(),
                                 flags,
                                 loader,
                                 elements);
    }

openDexFileNative是个native方法,不同的系统版本会有些差异

Android7.0

最终实现是dalvik_system_DexFile中DexFile_openDexFileNative

static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) {
  ScopedUtfChars sourceName(env, javaSourceName);
  ...
  NullableScopedUtfChars outputName(env, javaOutputName);
  if (env->ExceptionCheck()) {
    return 0;
  }
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  std::vector> dex_files;
  std::vector error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),outputName.c_str(),class_loader,dex_elements,&oat_file, &error_msgs);
  ...
}

在OpenDexFilesFromOat中

std::vector> OatFileManager::OpenDexFilesFromOat(
    const char* dex_location,
    const char* oat_location,
    jobject class_loader,
    jobjectArray dex_elements,
    const OatFile** out_oat_file,
    std::vector* error_msgs) {
        
         OatFileAssistant oat_file_assistant(dex_location,
                                      oat_location,
                                      kRuntimeISA,
                                      /*profile_changed*/false,
                                      !runtime->IsAotCompiler());

  std::string error_msg;
  //锁住oat_location
  if (!oat_file_assistant.Lock(/*out*/&error_msg)) {
  
  }
  ...
        
    }

接下来看看OatFileAssistant的实现

OatFileAssistant::OatFileAssistant(const char* dex_location,
                                   const char* oat_location,
                                   const InstructionSet isa,
                                   bool profile_changed,
                                   bool load_executable)
    : isa_(isa), profile_changed_(profile_changed), load_executable_(load_executable) {
...
  if (oat_location != nullptr) {
    cached_oat_file_name_ = std::string(oat_location);
    cached_oat_file_name_attempted_ = true;
    cached_oat_file_name_found_ = true;
  }
}

如果oat_location不为空cached_oat_file_name_attempted_设置为true,再看看OatFileName方法

const std::string* OatFileAssistant::OatFileName() {
  if (!cached_oat_file_name_attempted_) {
    cached_oat_file_name_attempted_ = true;
    std::string cache_dir = StringPrintf("%s%s",
        DalvikCacheDirectory().c_str(), GetInstructionSetString(isa_));
    std::string error_msg;
    cached_oat_file_name_found_ = GetDalvikCacheFilename(dex_location_.c_str(),
        cache_dir.c_str(), &cached_oat_file_name_, &error_msg);
        ...
  }
  return cached_oat_file_name_found_ ? &cached_oat_file_name_ : nullptr;
}

如果cached_oat_file_name_attempted_为ture,会使用传入的路径,否则调用路径如下

DalvikCacheDirectory->GetDalvikCache->GetDalvikCacheImpl

DalvikCacheDirectory 获取的就是 /data/dalvik-cache/ 目录,最终调用到art/runtime/utils.cc方法中


static std::string GetDalvikCacheImpl(const char* subdir,
                                      const bool create_if_absent,
                                      const bool abort_on_error) {
  CHECK(subdir != nullptr);
  const char* android_data = GetAndroidData();
  const std::string dalvik_cache_root(StringPrintf("%s/dalvik-cache/", android_data));
  const std::string dalvik_cache = dalvik_cache_root + subdir;
  if (!OS::DirectoryExists(dalvik_cache.c_str())) {
    if (!create_if_absent) {
      // TODO: Check callers. Traditional behavior is to not to abort, even when abort_on_error.
      return "";
    }

    // Don't create the system's /data/dalvik-cache/... because it needs special permissions.
    if (strcmp(android_data, "/data") == 0) {
      if (abort_on_error) {
        LOG(FATAL) << "Failed to find dalvik-cache directory " << dalvik_cache
                   << ", cannot create /data dalvik-cache.";
        UNREACHABLE();
      }
      return "";
    }

    int result = mkdir(dalvik_cache_root.c_str(), 0700);
    if (result != 0 && errno != EEXIST) {
      if (abort_on_error) {
        PLOG(FATAL) << "Failed to create dalvik-cache root directory " << dalvik_cache_root;
        UNREACHABLE();
      }
      return "";
    }

    result = mkdir(dalvik_cache.c_str(), 0700);
    if (result != 0) {
      if (abort_on_error) {
        PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache;
        UNREACHABLE();
      }
      return "";
    }
  }
  return dalvik_cache;
}

如果optimizedDirectory设置为null,则默认会放在/data/dalvik-cache/目录下

android8.0

Android8.0发生了一些变化,主要调用链如下

DexFile_openDexFileNative->
OpenDexFilesFromOat->
OatFileAssistant::OatFileAssistant->
OatFileAssistant::DexLocationToOdexFilename->
DexLocationToOdexNames

DexFile_openDexFileNative

tatic jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName ATTRIBUTE_UNUSED,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) {
     ...
     
  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);
     ...
                                    
    }    

在这个方法发生了一些变化,javaOutputName并没有使用,都是用默认路径,默认路径拼接在DexLocationToOdexNames方法中实现

static bool DexLocationToOdexNames(const std::string& location,
                                   InstructionSet isa,
                                   std::string* odex_filename,
                                   std::string* oat_dir,
                                   std::string* isa_dir,
                                   std::string* error_msg) {
    ...
  size_t pos = location.rfind('/');
  if (pos == std::string::npos) {
    *error_msg = "Dex location " + location + " has no directory.";
    return false;
  }
  //dex目录
  std::string dir = location.substr(0, pos+1);
  //dex目录/oat/
  dir += "oat";
  if (oat_dir != nullptr) {
    *oat_dir = dir;
  }
    //dex目录/oat/
  dir += "/" + std::string(GetInstructionSetString(isa));
  if (isa_dir != nullptr) {
    *isa_dir = dir;
  }

  // Get the base part of the file without the extension.
  std::string file = location.substr(pos+1);
  pos = file.rfind('.');
  if (pos == std::string::npos) {
    *error_msg = "Dex location " + location + " has no extension.";
    return false;
  }
  std::string base = file.substr(0, pos);

  *odex_filename = dir + "/" + base + ".odex";
  return true;
}

以上处理最终路径是dex目录/oat/,Android9.0、Android10.0最终路径一样

总结

相同点:

  • 两者可以实现的一样的功能,支持加载dex或是jar、apk、zip中包含多个dex压缩文件
  • 都是BaseDexClassLoader的派生类

不同点:

  • PathClassLoader不支持设置优化文件路径,而DexClassLoader必须设置支持路径。如果没设置优化路径,在Android5.0、Android6.0、Android7.0默认优化路径/data/dalvik-cache/,在Android8.0、Android9.0、Android10自己设置的优化目录没有使用,放到统一dex目录/oat/目录下

你可能感兴趣的:(DexClassLoader与PathClassLoader区别)