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<File> files,
            File optimizedDirectory) {
        ArrayList<Element> elements = new ArrayList<Element>();

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

推荐阅读更多精彩内容