日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

谈谈 Android 中的 PathClassLoader 和 DexClassLoader

發(fā)布時間:2023/12/15 Android 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谈谈 Android 中的 PathClassLoader 和 DexClassLoader 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這是 ZY 第 13 篇原創(chuàng)技術(shù)文章

預(yù)備知識

  • 了解 android 基本 ClassLoader 知識
  • 看完本文可以達(dá)到什么程度

  • 了解 PathClassLoader 和 DexClassLoader 區(qū)別
  • 文章概覽

    一、起因

    說起 Android 中的 PathClassLoader 和 DexClassLoader,先提出一個疑問,PathClassLoader 和 DexClassLoader 有什么區(qū)別呢?
    關(guān)于答案,我斗膽猜測一下,大家心中的回答一定是 PathClassLoader 是用來加載已經(jīng)安裝的 apk 的,DexClassLoader 是用來加載存儲空間的 dex / apk 文件的。為什么這樣說呢,因為之前我也一直這樣理解的,而且網(wǎng)上大部分文章中也都是這樣講解的。
    那為何突然又談起 PathClassLoader 和 DexClassLoader 呢?起因是我在前段時間寫了一些插件化的 demo,當(dāng)時忘記了 PathClassLoader 和 DexClassLoader 這回事,直接用 PathClassLoader 去加載插件了,竟然也可以加載成功???一絲絲的困惑浮現(xiàn)在我英俊帥氣的臉龐上,聰明的小腦瓜里打上了一個小小的問號。于是乎去翻了一下源碼,就有了這篇文章。

    二、先放結(jié)論

    先放結(jié)論,PathClassLoader 和 DexClassLoader 都能加載外部的 dex/apk,只不過區(qū)別是 DexClassLoader 可以指定 optimizedDirectory,也就是 dex2oat 的產(chǎn)物 .odex 存放的位置,而 PathClassLoader 只能使用系統(tǒng)默認(rèn)位置。但是這個 optimizedDirectory 在 Android 8.0 以后也被舍棄了,只能使用系統(tǒng)默認(rèn)的位置了。

    我們這里先基于 android 5.0 代碼來分析,然后再看看其他系統(tǒng)版本的一些區(qū)別。(選取 5.0 是因為此時 art 的源碼還比較簡單~)

    三、ClassLoader 的構(gòu)造函數(shù)

    3.1 BaseDexClassLoader 構(gòu)造函數(shù)

    PathClassLoader 和 DexClassLoader 都是繼承了 BaseDexClassLoader,這里先看一下。 BaseDexClassLoader 的構(gòu)造函數(shù)。

    public class BaseDexClassLoader extends ClassLoader {private final DexPathList pathList;/*** Constructs an instance.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param optimizedDirectory directory where optimized dex files* should be written; may be {@code null}* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);} } 復(fù)制代碼

    BaseDexClassLoader 構(gòu)造函數(shù)有四個參數(shù),含義如下:

    • dexPath: 需要加載的文件列表,文件可以是包含了 classes.dex 的 JAR/APK/ZIP,也可以直接使用 classes.dex 文件,多個文件用 “:” 分割
    • optimizedDirectory: 存放優(yōu)化后的 dex,可以為空
    • libraryPath: 存放需要加載的 native 庫的目錄
    • parent: 父 ClassLoader

    通過構(gòu)造函數(shù)我們大概可以了解到 BaseDexClassLoader 的運行方式,傳入 dex 文件,然后進(jìn)行優(yōu)化,保存優(yōu)化后的 dex 文件到 optimizedDirectory 目錄。

    3.2 PathClassLoader 構(gòu)造函數(shù)

    接著我們再看 PathClassLoader 的構(gòu)造函數(shù)。

    /*** Provides a simple {@link ClassLoader} implementation that operates on a list* of files and directories in the local file system, but does not attempt to* load classes from the network. Android uses this class for its system class* loader and for its application class loader(s).*/ public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ulyanzheng>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);} } 復(fù)制代碼

    關(guān)于 PathClassLoader 有一點稍微注意一下,代碼注釋中對 PathClassLoader 的介紹是,用來操作文件系統(tǒng)上的一系列文件和目錄 的 ClassLoader 實現(xiàn)。其中并沒有提到只能加載安裝后的 apk 文件。
    PathClassLoader 有兩個構(gòu)造函數(shù),區(qū)別在于傳給 BaseDexClassLoader 的 libraryPath 是否為空。最終調(diào)用 BaseDexClassLoader 構(gòu)造函數(shù)時,傳入的 optimizedDirectory 為空。

    3.3 DexClassLoader 構(gòu)造函數(shù)

    再來看看 DexClassLoader 的構(gòu)造函數(shù)。和 BaseDexClassLoader 構(gòu)造函數(shù)的參數(shù)是一樣的。

    public class DexClassLoader extends BaseDexClassLoader {/*** Creates a {@code DexClassLoader} that finds interpreted and native* code. Interpreted classes are found in a set of DEX files contained* in Jar or APK files.** <p>The path lists are separated using the character specified by the* {@code path.separator} system property, which defaults to {@code :}.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param optimizedDirectory directory where optimized dex files* should be written; must not be {@code null}* @param librarySearchPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);} } 復(fù)制代碼

    通過上面對構(gòu)造函數(shù)的分析,我們可以明白,PathClassLoader 和 DexClassLoader 關(guān)鍵不同點,在 optimizedDirectory 參數(shù)上,PathClassLoader 傳入的是 null,而 DexClassLoader 傳入的是用戶指定的目錄。

    四、optimizedDirectory 參數(shù)的處理

    既然知道了區(qū)別在 optimizedDirectory,那就來看看 BaseDexClassLoader 里是怎么處理 optimizedDirectory 的。

    4.1 DexPathList 處理

    在 BaseDexClassLoader 里,直接將 optimizedDirectory 透傳給了 DexPathList。 這里先簡單介紹一下 DexPathList。 DexPathList 里有兩個成員變量,dexElements 用來保存 dex 和資源列表,nativeLibraryDirectories 用來保存 native 庫列表。

    class DexPathList {private final Element[] dexElements;private final File[] nativeLibraryDirectories; } 復(fù)制代碼

    在 DexPathList 中,使用 optimizedDirectory 的路徑是:

    DexPathList -> makeDexElements -> loadDexFile 復(fù)制代碼

    這里要看一下 loadDexFile 方法。

    class DexPathList {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);}} } 復(fù)制代碼

    在 DexPathList 中,會為每一個 DEX 文件創(chuàng)建一個 DexFile 對象,創(chuàng)建方式有兩種,optimizedDirectory 為空時,調(diào)用 DexFile(file) 創(chuàng)建,否則調(diào)用 DexFile.loadDex()。
    這樣對于 optimizedDirectory 的處理就流轉(zhuǎn)到 DexFile 里了。

    4.2 DexFile 處理

    其實在 DexFile.loadDex 里,也是直接調(diào)用了 DexFile 的構(gòu)造函數(shù)

    class DexFile {public DexFile(File file) throws IOException {this(file.getPath());}public DexFile(String fileName) throws IOException {// 調(diào)用 openDexFile 處理 dexmCookie = openDexFile(fileName, null, 0);mFileName = fileName;guard.open("close");}private DexFile(String sourceName, String outputName, int flags) throws IOException {// ...// 調(diào)用 openDexFile 處理 dexmCookie = openDexFile(sourceName, outputName, flags);mFileName = sourceName;guard.open("close");}static public DexFile loadDex(String sourcePathName, String outputPathName,int flags) throws IOException {return new DexFile(sourcePathName, outputPathName, flags);}private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {// 最終調(diào)用 native 方法return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null) ? null : new File(outputName).getAbsolutePath(),flags);}private static native long openDexFileNative(String sourceName, String outputName, int flags); } 復(fù)制代碼

    DexFile 代碼不多,上面基本上就是主要代碼了。我們可以看到,不管調(diào)用 DexFile 哪個構(gòu)造函數(shù),最后都會通過 openDexFileNative 進(jìn)行處理,區(qū)別就在于 outputName 參數(shù)是否為空,而 outputName 參數(shù),就是上面一路傳遞下來的 optimizeDirectory 參數(shù)。
    我們再回顧一下調(diào)用的鏈路:

    PathClassLoader.constructor / DexClassLoader.constructor -> BaseDexClassLoader.constructor -> DexPathList.constructor -> DexPathList.makeDexElements -> DexPathList.loadDexFile -> DexFile.constructor / DexFile.loadDex -> DexFile.openDexFile -> DexFile.openDexFileNative 復(fù)制代碼

    再繼續(xù)往下看,就走到了 native 邏輯。native 邏輯可以下載 art 源碼對照查看。

    4.3 native 處理

    openDexFileNative 對應(yīng)的 native 邏輯在 dalvik_system_DexFile.cc 里的 DexFile_openDexFileNative 方法。
    在 DexFile_openDexFileNative 里主要做事情是處理 DEX 文件,并生成 .odex 文件到 optimizedDirectory 里。
    這里關(guān)于 optimizedDirectory 的處理路徑是:

    DexFile_openDexFileNative -> ClassLinker::OpenDexFilesFromOat 復(fù)制代碼

    在 OpenDexFilesFromOat 里有這樣一段處理邏輯:

    ClassLinker::OpenDexFilesFromOat() {// ...if (oat_location == nullptr) {// 如果 oat_location 為空,就使用默認(rèn)的 dalvikcache const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());oat_location = cache_location.c_str();}// ...if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {// Create the oat file.open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),oat_location, error_msgs));} } 復(fù)制代碼

    上面方法里的 oat_location 就是 optimizeDirectory 傳入到 native 中的化身。這里有一個判斷邏輯,如果 oat_location 為空的話,就采用默認(rèn)的 dalvikcache 路徑。之后調(diào)用 CreateOatFileForDexLocation 去優(yōu)化 DEX 文件了。
    而 dalvikcache 是通過 GetDalvikCacheOrDie 獲取的。

    // art/runtime/utils.cc std::string GetDalvikCacheOrDie(const char* subdir, const bool create_if_absent) {CHECK(subdir != nullptr);// 這里的 AndroidData 就是 /data 目錄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 (create_if_absent && !OS::DirectoryExists(dalvik_cache.c_str())) {// Don't create the system's /data/dalvik-cache/... because it needs special permissions.if (strcmp(android_data, "/data") != 0) {int result = mkdir(dalvik_cache_root.c_str(), 0700);if (result != 0 && errno != EEXIST) {PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache_root;return "";}result = mkdir(dalvik_cache.c_str(), 0700);if (result != 0) {PLOG(FATAL) << "Failed to create dalvik-cache directory " << dalvik_cache;return "";}} else {LOG(FATAL) << "Failed to find dalvik-cache directory " << dalvik_cache;return "";}}return dalvik_cache; } 復(fù)制代碼

    GetDalvikCacheOrDie 獲取的就是 /data/dalvik-cache/ 目錄。
    這里我們回顧一下之前提出的問題,避免迷失在茫茫代碼中。
    我們的問題是 optimizedDirectory 參數(shù)傳空和不為空有什么區(qū)別,PathClassLoader 傳入的 optmizedDirectory 為空,而 DexClassLoader 傳入的 optimizedDirectory 是用戶自定義的目錄。
    回看一下調(diào)用鏈路。

    PathClassLoader.constructor / DexClassLoader.constructor -> BaseDexClassLoader.constructor -> DexPathList.constructor -> DexPathList.makeDexElements -> DexPathList.loadDexFile -> DexFile.constructor / DexFile.loadDex -> DexFile.openDexFile -> DexFile.openDexFileNative -> DexFile_openDexFileNative -> ClassLinker::OpenDexFilesFromOat 復(fù)制代碼

    到這里我們就可以得出結(jié)論了,optmizedDirectory 不為空時,使用用戶定義的目錄作為 DEX 文件優(yōu)化后產(chǎn)物 .odex 的存儲目錄,為空時,會使用默認(rèn)的 /data/dalvik-cache/ 目錄。
    所以印證了開頭的結(jié)論,PathClassLoader 其實并不是只能加載安裝后的 APK,也可以加載其他 DEX/JAR/APK 文件,只不過生成的 .odex 文件只能存儲在系統(tǒng)默認(rèn)路徑下。
    被誤導(dǎo)多年的謎題終于解開了。耳邊不禁響起柯南破案的 BGM。

    五、其他系統(tǒng)版本上進(jìn)行驗證

    不過上述的分析是在 5.0 源碼下進(jìn)行的,我們再選取 4.4 和 8.0 看一下。
    為什么選取這兩個版本呢?首先 4.4 和 5.0 是 ART 和 Dalvik 的分水嶺,而 8.0 以后對 PathClassLoader 有些改動。

    5.1 Android 4.4

    有了上面的分析基礎(chǔ),我們分析 4.4 的代碼就順暢的多了。一路從 Java 分析到 native。 Java 層代碼沒有什么變動,native 的入口還是 DexFile_openDexFileNative。之后的代碼就有了些許不一樣。

    DexFile_openDexFileNative() {// ...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);}// ... } 復(fù)制代碼

    這里和 5.0 的區(qū)別就是 根據(jù) outputName 也就是 optimizedDirectory 是否為空,調(diào)用了兩個不同的函數(shù)。 而 FindDexFileInOatFileFromDexLocation 里的邏輯就又有些熟悉了。

    ClassLinker::FindDexFileInOatFileFromDexLocation() {// ...std::string oat_cache_filename(GetDalvikCacheFilenameOrDie(dex_location));return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_cache_filename); } 復(fù)制代碼

    默認(rèn)也是獲取到 dalvikcache 目錄作為 .odex 文件的存儲路徑。

    5.2 Android 8.0

    在 8.0 系統(tǒng)上,事情發(fā)生了一些微弱的變化,我們看看 BaseDexClassLoader 的構(gòu)造函數(shù)。

    class BaseDexClassLoader {/*** Constructs an instance.* Note that all the *.jar and *.apk files from {@code dexPath} might be* first extracted in-memory before the code is loaded. This can be avoided* by passing raw dex files (*.dex) in the {@code dexPath}.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android.* @param optimizedDirectory this parameter is deprecated and has no effect* @param librarySearchPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);if (reporter != null) {reporter.report(this.pathList.getDexPaths());}} } 復(fù)制代碼

    一個很明顯的變化就是,optimizedDirectory 被棄用了,傳給 DexPathList 的 optimizedDirectory 直接為空,不管外面?zhèn)鬟M(jìn)來什么值。 也就是說,在 8.0 上,PathClassLoader 和 DexClassLoader 其實已經(jīng)沒有什么區(qū)別了。DexClassLoader 也不能指定 optimizedDirectory 了。

    而在 DexFile_openDexFileNative 中,可以看到,javaOutputName 參數(shù)也已經(jīng)被棄用了。

    static jobject DexFile_openDexFileNative(JNIEnv* env,jclass,jstring javaSourceName,jstring javaOutputName ATTRIBUTE_UNUSED,jint flags ATTRIBUTE_UNUSED,jobject class_loader,jobjectArray dex_elements) { } 復(fù)制代碼

    之后對 DEX 文件的處理鏈路如下:

    DexFile_openDexFileNative -> DexLocationToOdexNames -> OatFileManager::OpenDexFilesFromOat -> OatFileAssistant::OatFileAssistant -> OatFileAssistant::DexLocationToOdexFilename -> DexLocationToOdexNames 復(fù)制代碼

    在 DexLocationToOdexNames 方法里,對 .odex 文件的路徑做了處理。

    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) {CHECK(odex_filename != nullptr);CHECK(error_msg != nullptr);// The odex file name is formed by replacing the dex_location extension with// .odex and inserting an oat/<isa> directory. For example:// location = /foo/bar/baz.jar// odex_location = /foo/bar/oat/<isa>/baz.odex// Find the directory portion of the dex location and add the oat/<isa>// directory.size_t pos = location.rfind('/');if (pos == std::string::npos) {*error_msg = "Dex location " + location + " has no directory.";return false;}std::string dir = location.substr(0, pos+1);// Add the oat directory.dir += "oat";if (oat_dir != nullptr) {*oat_dir = dir;}// Add the isa directorydir += "/" + 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; } 復(fù)制代碼

    看到上面的處理就是在 DEX 文件同級目錄下添加一個 oat/ 文件作為 .odex 的存儲目錄。

    總結(jié)

    關(guān)于我

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

    總結(jié)

    以上是生活随笔為你收集整理的谈谈 Android 中的 PathClassLoader 和 DexClassLoader的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。