做Android插件框架时,经常会用到dex的动态加载,就要直接或间接的使用DexClassLoader,在new DexClassLoader的时候Android系统做了很多工作,下面我们详细分析一下:
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); }
......
@Override protected URL findResource(String name) { return pathList.findResource(name); } @Override protected Enumeration<URL> findResources(String name) { return pathList.findResources(name); } @Override public String findLibrary(String name) { return pathList.findLibrary(name); }
......
}
看到关键步骤了,设置完parent的ClassLoader之后,创建了DexPathList对象pathList,可以看到,很多操作都是直接委托给pathList的,我们看下这个对象里面做了什么。
这里涉及到两个目录,optimizedDirectory和libraryPath,我们先从简单的入手,先看看DexPathList是如何处理libraryPath的。
final class DexPathList { private static final String DEX_SUFFIX = ".dex";/** List of native library directories. */ private final File[] nativeLibraryDirectories;public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ...... this.nativeLibraryDirectories = splitLibraryPath(libraryPath); } private static File[] splitLibraryPath(String path) { // Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. this class loader's library path for application libraries // 2. the VM's library path from the system property for system libraries // // This order was reversed prior to Gingerbread; see http://b/2933456. ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true); return result.toArray(new File[result.size()]); } private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; } private static void splitAndAdd(String searchPath, boolean directoriesOnly, ArrayList<File> resultList) { if (searchPath == null) { return; } for (String path : searchPath.split(":")) { try { StructStat sb = Libcore.os.stat(path); if (!directoriesOnly || S_ISDIR(sb.st_mode)) { resultList.add(new File(path)); } } catch (ErrnoException ignored) { } } } }
从上面代码可以看到,系统会将用户传进的目录和默认的系统lib目录(System.getProperty("java.library.path"))通过“:”分割后,存进一个File数组中。
这就是DexClassLoader对libraryPath的所有操作,所以可以看到,和optimizePath不一样,并没有对so文件解压。
接下来,我们看看DexClassLoader初始化时候对optimizePath做了什么:
还是从DexPathList入手
final class DexPathList { /** class definition context */ private final ClassLoader definingContext; private final Element[] dexElements; private final IOException[] dexElementsSuppressedExceptions; public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ...... //check optimized directory ...... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); //save Exceptions ...... this.nativeLibraryDirectories = splitLibraryPath(libraryPath); } private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { ...... 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 { zip = file; try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } } ...... 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,从而进入到DexFile中,我们继续。
public final class DexFile { 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 + " fileName=" + fileName); } 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 + " sourceName=" + sourceName + " outputName=" + outputName); } 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 { // Use absolute paths to enable the use of relative paths when testing on host. return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags); } }
可以看到,最终走到openDexFileNative这个native方法中,这个方法的native实现在art/runtime/native/dalvik_system_DexFile.cc中,看代码:
static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) { ...... bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs, dex_files.get()); ...... }
方法OpenDexFilesFromOat的实现在art/runtime/class_linker.cc中,这个方法太长,我先把全部内容贴上来,
1 bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location, 2 std::vector<std::string>* error_msgs, 3 std::vector<std::unique_ptr<const DexFile>>* dex_files) { 4 // 1) Check whether we have an open oat file. 5 // This requires a dex checksum, use the "primary" one. 6 uint32_t dex_location_checksum; 7 uint32_t* dex_location_checksum_pointer = &dex_location_checksum; 8 bool have_checksum = true; 9 std::string checksum_error_msg; 10 if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) { 11 // This happens for pre-opted files since the corresponding dex files are no longer on disk. 12 dex_location_checksum_pointer = nullptr; 13 have_checksum = false; 14 } 15 16 bool needs_registering = false; 17 18 const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, 19 dex_location_checksum_pointer); 20 std::unique_ptr<const OatFile> open_oat_file( 21 oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr); 22 23 // 2) If we do not have an open one, maybe there's one on disk already. 24 25 // In case the oat file is not open, we play a locking game here so 26 // that if two different processes race to load and register or generate 27 // (or worse, one tries to open a partial generated file) we will be okay. 28 // This is actually common with apps that use DexClassLoader to work 29 // around the dex method reference limit and that have a background 30 // service running in a separate process. 31 ScopedFlock scoped_flock; 32 33 if (open_oat_file.get() == nullptr) { 34 if (oat_location != nullptr) { 35 // Can only do this if we have a checksum, else error. 36 if (!have_checksum) { 37 error_msgs->push_back(checksum_error_msg); 38 return false; 39 } 40 41 std::string error_msg; 42 43 // We are loading or creating one in the future. Time to set up the file lock. 44 if (!scoped_flock.Init(oat_location, &error_msg)) { 45 error_msgs->push_back(error_msg); 46 return false; 47 } 48 49 // TODO Caller specifically asks for this oat_location. We should honor it. Probably? 50 open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum, 51 oat_location, &error_msg)); 52 53 if (open_oat_file.get() == nullptr) { 54 std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s", 55 dex_location, oat_location, error_msg.c_str()); 56 VLOG(class_linker) << compound_msg; 57 error_msgs->push_back(compound_msg); 58 } 59 } else { 60 // TODO: What to lock here? 61 bool obsolete_file_cleanup_failed; 62 open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location, 63 dex_location_checksum_pointer, 64 kRuntimeISA, error_msgs, 65 &obsolete_file_cleanup_failed)); 66 // There's no point in going forward and eventually try to regenerate the 67 // file if we couldn't remove the obsolete one. Mostly likely we will fail 68 // with the same error when trying to write the new file. 69 // TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS). 70 if (obsolete_file_cleanup_failed) { 71 return false; 72 } 73 } 74 needs_registering = true; 75 } 76 77 // 3) If we have an oat file, check all contained multidex files for our dex_location. 78 // Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument. 79 bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, 80 dex_location_checksum_pointer, 81 false, error_msgs, dex_files); 82 if (success) { 83 const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it. 84 if (needs_registering) { 85 // We opened the oat file, so we must register it. 86 RegisterOatFile(oat_file); 87 } 88 // If the file isn't executable we failed patchoat but did manage to get the dex files. 89 return oat_file->IsExecutable(); 90 } else { 91 if (needs_registering) { 92 // We opened it, delete it. 93 open_oat_file.reset(); 94 } else { 95 open_oat_file.release(); // Do not delete open oat files. 96 } 97 } 98 99 // 4) If it's not the case (either no oat file or mismatches), regenerate and load. 100 101 // Need a checksum, fail else. 102 if (!have_checksum) { 103 error_msgs->push_back(checksum_error_msg); 104 return false; 105 } 106 107 // Look in cache location if no oat_location is given. 108 std::string cache_location; 109 if (oat_location == nullptr) { 110 // Use the dalvik cache. 111 const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA))); 112 cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str()); 113 oat_location = cache_location.c_str(); 114 } 115 116 bool has_flock = true; 117 // Definitely need to lock now. 118 if (!scoped_flock.HasFile()) { 119 std::string error_msg; 120 if (!scoped_flock.Init(oat_location, &error_msg)) { 121 error_msgs->push_back(error_msg); 122 has_flock = false; 123 } 124 } 125 126 if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) { 127 // Create the oat file. 128 open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(), 129 oat_location, error_msgs)); 130 } 131 132 // Failed, bail. 133 if (open_oat_file.get() == nullptr) { 134 std::string error_msg; 135 // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress. 136 DexFile::Open(dex_location, dex_location, &error_msg, dex_files); 137 error_msgs->push_back(error_msg); 138 return false; 139 } 140 141 // Try to load again, but stronger checks. 142 success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, 143 dex_location_checksum_pointer, 144 true, error_msgs, dex_files); 145 if (success) { 146 RegisterOatFile(open_oat_file.release()); 147 return true; 148 } else { 149 return false; 150 } 151 }