寫DexClassLoader加載class主要是為寫Android插件化做准備:
我們在看DexClassLoader加載class之前,先大概了解下ClassLoader
ClassLoader源碼:http://www.cnblogs.com/kangqi001/p/8318113.html
我們都知道Android中Class的加載時執行的
ClassLoader.loadClass(String name)方法()
這里我們看下ClassLoader的loadClass方法:
468 public Class<?> loadClass(String className) throws ClassNotFoundException { 469 return loadClass(className, false); 470 } 498 protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 499 Class<?> clazz = findLoadedClass(className);//查看類是否以前被加載個 500 501 if (clazz == null) { 502 ClassNotFoundException suppressed = null; 503 try { 504 clazz = parent.loadClass(className, false);//先調用父類加載器去加載,可有效加載相關系統類(這個是Android插件化的一個重要機制) 505 } catch (ClassNotFoundException e) { 506 suppressed = e; 507 } 508 509 if (clazz == null) { 510 try { 511 clazz = findClass(className);//父類加載沒有找到類 就調用自己的findClass機制 512 } catch (ClassNotFoundException e) { 513 e.addSuppressed(suppressed); 514 throw e; 515 } 516 } 517 } 518 519 return clazz; 520 }
這里主要查看它511行代碼:直接調用的類本身findClass方法(上面是雙親委派機制)
DexClassLoader.java源碼:
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 libraryPath 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 libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } }
DexClassLoader構造方法參數說明:
dexPath:apk/dex/jar文件路徑
optimizedDirectory:文件解壓路徑(這個路徑下保存的是.dex文件不是.class)
libraryPath:加載時用到的so庫(可能理解有問題)
parent:父加載器(這個比較重要與Android加載class的機制有關)
從DexClassLoader源碼可以知道 DexClassLoader直接實現的是BaseDexClassLoader ,所以需要我們進一步去查看BaseDexClassLoader 的源碼
BaseDexClassLoader 源碼:
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package dalvik.system; 18 19import java.io.File; 20import java.net.URL; 21import java.util.ArrayList; 22import java.util.Enumeration; 23import java.util.List; 24 25/** 26 * Base class for common functionality between various dex-based 27 * {@link ClassLoader} implementations. 28 */ 29public class BaseDexClassLoader extends ClassLoader { 30 private final DexPathList pathList; 31 32 /** 33 * Constructs an instance. 34 * 35 * @param dexPath the list of jar/apk files containing classes and 36 * resources, delimited by {@code File.pathSeparator}, which 37 * defaults to {@code ":"} on Android 38 * @param optimizedDirectory directory where optimized dex files 39 * should be written; may be {@code null} 40 * @param libraryPath the list of directories containing native 41 * libraries, delimited by {@code File.pathSeparator}; may be 42 * {@code null} 43 * @param parent the parent class loader 44 */ 45 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 46 String libraryPath, ClassLoader parent) { 47 super(parent); 48 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); 49 } 50 51 @Override 52 protected Class<?> findClass(String name) throws ClassNotFoundException { 53 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 54 Class c = pathList.findClass(name, suppressedExceptions); 55 if (c == null) { 56 ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); 57 for (Throwable t : suppressedExceptions) { 58 cnfe.addSuppressed(t); 59 } 60 throw cnfe; 61 } 62 return c; 63 } 64 65 @Override 66 protected URL findResource(String name) { 67 return pathList.findResource(name); 68 } 69 70 @Override 71 protected Enumeration<URL> findResources(String name) { 72 return pathList.findResources(name); 73 } 74 75 @Override 76 public String findLibrary(String name) { 77 return pathList.findLibrary(name); 78 } 79 80 /** 81 * Returns package information for the given package. 82 * Unfortunately, instances of this class don't really have this 83 * information, and as a non-secure {@code ClassLoader}, it isn't 84 * even required to, according to the spec. Yet, we want to 85 * provide it, in order to make all those hopeful callers of 86 * {@code myClass.getPackage().getName()} happy. Thus we construct 87 * a {@code Package} object the first time it is being requested 88 * and fill most of the fields with dummy values. The {@code 89 * Package} object is then put into the {@code ClassLoader}'s 90 * package cache, so we see the same one next time. We don't 91 * create {@code Package} objects for {@code null} arguments or 92 * for the default package. 93 * 94 * <p>There is a limited chance that we end up with multiple 95 * {@code Package} objects representing the same package: It can 96 * happen when when a package is scattered across different JAR 97 * files which were loaded by different {@code ClassLoader} 98 * instances. This is rather unlikely, and given that this whole 99 * thing is more or less a workaround, probably not worth the 100 * effort to address. 101 * 102 * @param name the name of the class 103 * @return the package information for the class, or {@code null} 104 * if there is no package information available for it 105 */ 106 @Override 107 protected synchronized Package getPackage(String name) { 108 if (name != null && !name.isEmpty()) { 109 Package pack = super.getPackage(name); 110 111 if (pack == null) { 112 pack = definePackage(name, "Unknown", "0.0", "Unknown", 113 "Unknown", "0.0", "Unknown", null); 114 } 115 116 return pack; 117 } 118 119 return null; 120 } 121 122 /** 123 * @hide 124 */ 125 public String getLdLibraryPath() { 126 StringBuilder result = new StringBuilder(); 127 for (File directory : pathList.getNativeLibraryDirectories()) { 128 if (result.length() > 0) { 129 result.append(':'); 130 } 131 result.append(directory); 132 } 133 return result.toString(); 134 } 135 136 @Override public String toString() { 137 return getClass().getName() + "[" + pathList + "]"; 138 } 139} 140
DexClassLoader的構造方法執行的是BaseDexClassLoader中的:
45 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 46 String libraryPath, ClassLoader parent) { 47 super(parent); 48 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); 49 }
這里主要查看48行代碼:
它去實例化一個DexPathList對象(重要)
BaseDexClassLoader重寫了findClass方法
51 @Override 52 protected Class<?> findClass(String name) throws ClassNotFoundException { 53 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 54 Class c = pathList.findClass(name, suppressedExceptions); 55 if (c == null) { 56 ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); 57 for (Throwable t : suppressedExceptions) { 58 cnfe.addSuppressed(t); 59 } 60 throw cnfe; 61 } 62 return c; 63 }
這里主要看54行代碼:Class c = pathList.findClass(name, suppressedExceptions);
BaseDexClassLoader的findClass方法:
實際實現的是DexPathList的findClass方法
到這里我們知道了一個這樣的流程:DexClassLoader去加載一個類:
DexClassLoader.loadClass(String className)---->實際執行的是ClassLoader.loadClass(String className)方法
ClassLoader.loadClass(String className)方法中 又調用了 BaseDexClassLoader.findClass(String className)方法(BaseDexClassLoader重寫了findClass方法)
而在BaseDexClassLoader的findClass方法中又執行了DexPathList的findClass方法
方法執行流程:ClassLoader.loadClass --BaseDexClassLoader.findClass---DexPathList.findClass
小結:到這里我們知道了DexClassLoader加載類的 核心就是 DexPathList類
我們進一步去查看DexPathList的源碼:
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package dalvik.system; 18 19import java.io.File; 20import java.io.IOException; 21import java.net.MalformedURLException; 22import java.net.URL; 23import java.util.ArrayList; 24import java.util.Arrays; 25import java.util.Collections; 26import java.util.Enumeration; 27import java.util.List; 28import java.util.zip.ZipFile; 29import libcore.io.ErrnoException; 30import libcore.io.IoUtils; 31import libcore.io.Libcore; 32import libcore.io.StructStat; 33import static libcore.io.OsConstants.*; 34 35/** 36 * A pair of lists of entries, associated with a {@code ClassLoader}. 37 * One of the lists is a dex/resource path — typically referred 38 * to as a "class path" — list, and the other names directories 39 * containing native code libraries. Class path entries may be any of: 40 * a {@code .jar} or {@code .zip} file containing an optional 41 * top-level {@code classes.dex} file as well as arbitrary resources, 42 * or a plain {@code .dex} file (with no possibility of associated 43 * resources). 44 * 45 * <p>This class also contains methods to use these lists to look up 46 * classes and resources.</p> 47 */ 48/*package*/ final class DexPathList { 49 private static final String DEX_SUFFIX = ".dex"; 50 private static final String JAR_SUFFIX = ".jar"; 51 private static final String ZIP_SUFFIX = ".zip"; 52 private static final String APK_SUFFIX = ".apk"; 53 54 /** class definition context */ 55 private final ClassLoader definingContext; 56 57 /** 58 * List of dex/resource (class path) elements. 59 * Should be called pathElements, but the Facebook app uses reflection 60 * to modify 'dexElements' (http://b/7726934). 61 */ 62 private final Element[] dexElements; 63 64 /** List of native library directories. */ 65 private final File[] nativeLibraryDirectories; 66 67 /** 68 * Exceptions thrown during creation of the dexElements list. 69 */ 70 private final IOException[] dexElementsSuppressedExceptions; 71 72 /** 73 * Constructs an instance. 74 * 75 * @param definingContext the context in which any as-yet unresolved 76 * classes should be defined 77 * @param dexPath list of dex/resource path elements, separated by 78 * {@code File.pathSeparator} 79 * @param libraryPath list of native library directory path elements, 80 * separated by {@code File.pathSeparator} 81 * @param optimizedDirectory directory where optimized {@code .dex} files 82 * should be found and written to, or {@code null} to use the default 83 * system directory for same 84 */ 85 public DexPathList(ClassLoader definingContext, String dexPath, 86 String libraryPath, File optimizedDirectory) { 87 if (definingContext == null) { 88 throw new NullPointerException("definingContext == null"); 89 } 90 91 if (dexPath == null) { 92 throw new NullPointerException("dexPath == null"); 93 } 94 95 if (optimizedDirectory != null) { 96 if (!optimizedDirectory.exists()) { 97 throw new IllegalArgumentException( 98 "optimizedDirectory doesn't exist: " 99 + optimizedDirectory); 100 } 101 102 if (!(optimizedDirectory.canRead() 103 && optimizedDirectory.canWrite())) { 104 throw new IllegalArgumentException( 105 "optimizedDirectory not readable/writable: " 106 + optimizedDirectory); 107 } 108 } 109 110 this.definingContext = definingContext; 111 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 112 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 113 suppressedExceptions); 114 if (suppressedExceptions.size() > 0) { 115 this.dexElementsSuppressedExceptions = 116 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 117 } else { 118 dexElementsSuppressedExceptions = null; 119 } 120 this.nativeLibraryDirectories = splitLibraryPath(libraryPath); 121 } 122 123 @Override public String toString() { 124 return "DexPathList[" + Arrays.toString(dexElements) + 125 ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]"; 126 } 127 128 /** 129 * For BaseDexClassLoader.getLdLibraryPath. 130 */ 131 public File[] getNativeLibraryDirectories() { 132 return nativeLibraryDirectories; 133 } 134 135 /** 136 * Splits the given dex path string into elements using the path 137 * separator, pruning out any elements that do not refer to existing 138 * and readable files. (That is, directories are not included in the 139 * result.) 140 */ 141 private static ArrayList<File> splitDexPath(String path) { 142 return splitPaths(path, null, false); 143 } 144 145 /** 146 * Splits the given library directory path string into elements 147 * using the path separator ({@code File.pathSeparator}, which 148 * defaults to {@code ":"} on Android, appending on the elements 149 * from the system library path, and pruning out any elements that 150 * do not refer to existing and readable directories. 151 */ 152 private static File[] splitLibraryPath(String path) { 153 // Native libraries may exist in both the system and 154 // application library paths, and we use this search order: 155 // 156 // 1. this class loader's library path for application libraries 157 // 2. the VM's library path from the system property for system libraries 158 // 159 // This order was reversed prior to Gingerbread; see http://b/2933456. 160 ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true); 161 return result.toArray(new File[result.size()]); 162 } 163 164 /** 165 * Splits the given path strings into file elements using the path 166 * separator, combining the results and filtering out elements 167 * that don't exist, aren't readable, or aren't either a regular 168 * file or a directory (as specified). Either string may be empty 169 * or {@code null}, in which case it is ignored. If both strings 170 * are empty or {@code null}, or all elements get pruned out, then 171 * this returns a zero-element list. 172 */ 173 private static ArrayList<File> splitPaths(String path1, String path2, 174 boolean wantDirectories) { 175 ArrayList<File> result = new ArrayList<File>(); 176 177 splitAndAdd(path1, wantDirectories, result); 178 splitAndAdd(path2, wantDirectories, result); 179 return result; 180 } 181 182 /** 183 * Helper for {@link #splitPaths}, which does the actual splitting 184 * and filtering and adding to a result. 185 */ 186 private static void splitAndAdd(String searchPath, boolean directoriesOnly, 187 ArrayList<File> resultList) { 188 if (searchPath == null) { 189 return; 190 } 191 for (String path : searchPath.split(":")) { 192 try { 193 StructStat sb = Libcore.os.stat(path); 194 if (!directoriesOnly || S_ISDIR(sb.st_mode)) { 195 resultList.add(new File(path)); 196 } 197 } catch (ErrnoException ignored) { 198 } 199 } 200 } 201 202 /** 203 * Makes an array of dex/resource path elements, one per element of 204 * the given array. 205 */ 206 private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, 207 ArrayList<IOException> suppressedExceptions) { 208 ArrayList<Element> elements = new ArrayList<Element>(); 209 /* 210 * Open all files and load the (direct or contained) dex files 211 * up front. 212 */ 213 for (File file : files) { 214 File zip = null; 215 DexFile dex = null; 216 String name = file.getName(); 217 218 if (name.endsWith(DEX_SUFFIX)) { 219 // Raw dex file (not inside a zip/jar). 220 try { 221 dex = loadDexFile(file, optimizedDirectory); 222 } catch (IOException ex) { 223 System.logE("Unable to load dex file: " + file, ex); 224 } 225 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) 226 || name.endsWith(ZIP_SUFFIX)) { 227 zip = file; 228 229 try { 230 dex = loadDexFile(file, optimizedDirectory); 231 } catch (IOException suppressed) { 232 /* 233 * IOException might get thrown "legitimately" by the DexFile constructor if the 234 * zip file turns out to be resource-only (that is, no classes.dex file in it). 235 * Let dex == null and hang on to the exception to add to the tea-leaves for 236 * when findClass returns null. 237 */ 238 suppressedExceptions.add(suppressed); 239 } 240 } else if (file.isDirectory()) { 241 // We support directories for looking up resources. 242 // This is only useful for running libcore tests. 243 elements.add(new Element(file, true, null, null)); 244 } else { 245 System.logW("Unknown file type for: " + file); 246 } 247 248 if ((zip != null) || (dex != null)) { 249 elements.add(new Element(file, false, zip, dex)); 250 } 251 } 252 253 return elements.toArray(new Element[elements.size()]); 254 } 255 256 /** 257 * Constructs a {@code DexFile} instance, as appropriate depending 258 * on whether {@code optimizedDirectory} is {@code null}. 259 */ 260 private static DexFile loadDexFile(File file, File optimizedDirectory) 261 throws IOException { 262 if (optimizedDirectory == null) { 263 return new DexFile(file); 264 } else { 265 String optimizedPath = optimizedPathFor(file, optimizedDirectory); 266 return DexFile.loadDex(file.getPath(), optimizedPath, 0); 267 } 268 } 269 270 /** 271 * Converts a dex/jar file path and an output directory to an 272 * output file path for an associated optimized dex file. 273 */ 274 private static String optimizedPathFor(File path, 275 File optimizedDirectory) { 276 /* 277 * Get the filename component of the path, and replace the 278 * suffix with ".dex" if that's not already the suffix. 279 * 280 * We don't want to use ".odex", because the build system uses 281 * that for files that are paired with resource-only jar 282 * files. If the VM can assume that there's no classes.dex in 283 * the matching jar, it doesn't need to open the jar to check 284 * for updated dependencies, providing a slight performance 285 * boost at startup. The use of ".dex" here matches the use on 286 * files in /data/dalvik-cache. 287 */ 288 String fileName = path.getName(); 289 if (!fileName.endsWith(DEX_SUFFIX)) { 290 int lastDot = fileName.lastIndexOf("."); 291 if (lastDot < 0) { 292 fileName += DEX_SUFFIX; 293 } else { 294 StringBuilder sb = new StringBuilder(lastDot + 4); 295 sb.append(fileName, 0, lastDot); 296 sb.append(DEX_SUFFIX); 297 fileName = sb.toString(); 298 } 299 } 300 301 File result = new File(optimizedDirectory, fileName); 302 return result.getPath(); 303 } 304 305 /** 306 * Finds the named class in one of the dex files pointed at by 307 * this instance. This will find the one in the earliest listed 308 * path element. If the class is found but has not yet been 309 * defined, then this method will define it in the defining 310 * context that this instance was constructed with. 311 * 312 * @param name of class to find 313 * @param suppressed exceptions encountered whilst finding the class 314 * @return the named class or {@code null} if the class is not 315 * found in any of the dex files 316 */ 317 public Class findClass(String name, List<Throwable> suppressed) { 318 for (Element element : dexElements) { 319 DexFile dex = element.dexFile; 320 321 if (dex != null) { 322 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 323 if (clazz != null) { 324 return clazz; 325 } 326 } 327 } 328 if (dexElementsSuppressedExceptions != null) { 329 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 330 } 331 return null; 332 } 333 334 /** 335 * Finds the named resource in one of the zip/jar files pointed at 336 * by this instance. This will find the one in the earliest listed 337 * path element. 338 * 339 * @return a URL to the named resource or {@code null} if the 340 * resource is not found in any of the zip/jar files 341 */ 342 public URL findResource(String name) { 343 for (Element element : dexElements) { 344 URL url = element.findResource(name); 345 if (url != null) { 346 return url; 347 } 348 } 349 350 return null; 351 } 352 353 /** 354 * Finds all the resources with the given name, returning an 355 * enumeration of them. If there are no resources with the given 356 * name, then this method returns an empty enumeration. 357 */ 358 public Enumeration<URL> findResources(String name) { 359 ArrayList<URL> result = new ArrayList<URL>(); 360 361 for (Element element : dexElements) { 362 URL url = element.findResource(name); 363 if (url != null) { 364 result.add(url); 365 } 366 } 367 368 return Collections.enumeration(result); 369 } 370 371 /** 372 * Finds the named native code library on any of the library 373 * directories pointed at by this instance. This will find the 374 * one in the earliest listed directory, ignoring any that are not 375 * readable regular files. 376 * 377 * @return the complete path to the library or {@code null} if no 378 * library was found 379 */ 380 public String findLibrary(String libraryName) { 381 String fileName = System.mapLibraryName(libraryName); 382 for (File directory : nativeLibraryDirectories) { 383 String path = new File(directory, fileName).getPath(); 384 if (IoUtils.canOpenReadOnly(path)) { 385 return path; 386 } 387 } 388 return null; 389 } 390 391 /** 392 * Element of the dex/resource file path 393 */ 394 /*package*/ static class Element { 395 private final File file; 396 private final boolean isDirectory; 397 private final File zip; 398 private final DexFile dexFile; 399 400 private ZipFile zipFile; 401 private boolean initialized; 402 403 public Element(File file, boolean isDirectory, File zip, DexFile dexFile) { 404 this.file = file; 405 this.isDirectory = isDirectory; 406 this.zip = zip; 407 this.dexFile = dexFile; 408 } 409 410 @Override public String toString() { 411 if (isDirectory) { 412 return "directory \"" + file + "\""; 413 } else if (zip != null) { 414 return "zip file \"" + zip + "\""; 415 } else { 416 return "dex file \"" + dexFile + "\""; 417 } 418 } 419 420 public synchronized void maybeInit() { 421 if (initialized) { 422 return; 423 } 424 425 initialized = true; 426 427 if (isDirectory || zip == null) { 428 return; 429 } 430 431 try { 432 zipFile = new ZipFile(zip); 433 } catch (IOException ioe) { 434 /* 435 * Note: ZipException (a subclass of IOException) 436 * might get thrown by the ZipFile constructor 437 * (e.g. if the file isn't actually a zip/jar 438 * file). 439 */ 440 System.logE("Unable to open zip file: " + file, ioe); 441 zipFile = null; 442 } 443 } 444 445 public URL findResource(String name) { 446 maybeInit(); 447 448 // We support directories so we can run tests and/or legacy code 449 // that uses Class.getResource. 450 if (isDirectory) { 451 File resourceFile = new File(file, name); 452 if (resourceFile.exists()) { 453 try { 454 return resourceFile.toURI().toURL(); 455 } catch (MalformedURLException ex) { 456 throw new RuntimeException(ex); 457 } 458 } 459 } 460 461 if (zipFile == null || zipFile.getEntry(name) == null) { 462 /* 463 * Either this element has no zip/jar file (first 464 * clause), or the zip/jar file doesn't have an entry 465 * for the given name (second clause). 466 */ 467 return null; 468 } 469 470 try { 471 /* 472 * File.toURL() is compliant with RFC 1738 in 473 * always creating absolute path names. If we 474 * construct the URL by concatenating strings, we 475 * might end up with illegal URLs for relative 476 * names. 477 */ 478 return new URL("jar:" + file.toURL() + "!/" + name); 479 } catch (MalformedURLException ex) { 480 throw new RuntimeException(ex); 481 } 482 } 483 } 484}
這里我們先看它的構造方法:
85 public DexPathList(ClassLoader definingContext, String dexPath, 86 String libraryPath, File optimizedDirectory) {// 87 if (definingContext == null) { 88 throw new NullPointerException("definingContext == null"); 89 } 90 91 if (dexPath == null) { 92 throw new NullPointerException("dexPath == null"); 93 } 94 95 if (optimizedDirectory != null) { 96 if (!optimizedDirectory.exists()) { 97 throw new IllegalArgumentException( 98 "optimizedDirectory doesn't exist: " 99 + optimizedDirectory); 100 } 101 102 if (!(optimizedDirectory.canRead() 103 && optimizedDirectory.canWrite())) { 104 throw new IllegalArgumentException( 105 "optimizedDirectory not readable/writable: " 106 + optimizedDirectory); 107 } 108 } 109 110 this.definingContext = definingContext; 111 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 112 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 113 suppressedExceptions);//解壓APK/jar添加dex到dexElements數組中去 114 if (suppressedExceptions.size() > 0) { 115 this.dexElementsSuppressedExceptions = 116 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 117 } else { 118 dexElementsSuppressedExceptions = null; 119 } 120 this.nativeLibraryDirectories = splitLibraryPath(libraryPath); 121 }
參數說明:
參數一:BaseDexClassLoader本身
參數二:apk/dex/jar路徑
參數三:系統的一些文件庫
參數四:dex文件路徑(參數二apk或jar解壓輸出文件庫路徑)
通過DexPathList的構造方法
112行通過makeDexelements(...)方法我們獲取了一個 Element[] dexElements數組。保存了dex文件的相關信息
我們上面說到:DexClassLoader加載類實際調用的是DexPathList的findClass進行加載的。我們再來看下DexPathList的findClass方法
317 public Class findClass(String name, List<Throwable> suppressed) { 318 for (Element element : dexElements) {//這里的代碼比較重要 有一種插件化的思想就是把修改過的dex進行插隊 319 DexFile dex = element.dexFile; 320 321 if (dex != null) { 322 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 323 if (clazz != null) { 324 return clazz; 325 } 326 } 327 } 328 if (dexElementsSuppressedExceptions != null) { 329 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 330 } 331 return null; 332 }
重要思想:dex插隊處理,比如A.dex中包含了一個a.class文件。我們通過插入一個B.dex在A.dex文件之前,
findClass方法在遍歷DEXeLEMENTS文件會先在B.dex中查找,如果找到了a.class文件就會直接返回該a.class,如果沒有找到才去B.dex中查找
言歸正傳:
從322行代碼可知,類在dex中的加載 是執行的DexFile類的loadClassBinaryName方法
我們就需要進一步查看DexFile源碼:
public final class DexFile { 37 private int mCookie; 38 private final String mFileName; 39 private final CloseGuard guard = CloseGuard.get(); 40 41 /** 42 * Opens a DEX file from a given File object. This will usually be a ZIP/JAR 43 * file with a "classes.dex" inside. 44 * 45 * The VM will generate the name of the corresponding file in 46 * /data/dalvik-cache and open it, possibly creating or updating 47 * it first if system permissions allow. Don't pass in the name of 48 * a file in /data/dalvik-cache, as the named file is expected to be 49 * in its original (pre-dexopt) state. 50 * 51 * @param file 52 * the File object referencing the actual DEX file 53 * 54 * @throws IOException 55 * if an I/O error occurs, such as the file not being found or 56 * access rights missing for opening it 57 */ 58 public DexFile(File file) throws IOException { 59 this(file.getPath()); 60 } 61 62 /** 63 * Opens a DEX file from a given filename. This will usually be a ZIP/JAR 64 * file with a "classes.dex" inside. 65 * 66 * The VM will generate the name of the corresponding file in 67 * /data/dalvik-cache and open it, possibly creating or updating 68 * it first if system permissions allow. Don't pass in the name of 69 * a file in /data/dalvik-cache, as the named file is expected to be 70 * in its original (pre-dexopt) state. 71 * 72 * @param fileName 73 * the filename of the DEX file 74 * 75 * @throws IOException 76 * if an I/O error occurs, such as the file not being found or 77 * access rights missing for opening it 78 */ 79 public DexFile(String fileName) throws IOException { 80 mCookie = openDexFile(fileName, null, 0); 81 mFileName = fileName; 82 guard.open("close"); 83 //System.out.println("DEX FILE cookie is " + mCookie); 84 } 85 86 /** 87 * Opens a DEX file from a given filename, using a specified file 88 * to hold the optimized data. 89 * 90 * @param sourceName 91 * Jar or APK file with "classes.dex". 92 * @param outputName 93 * File that will hold the optimized form of the DEX data. 94 * @param flags 95 * Enable optional features. 96 */ 97 private DexFile(String sourceName, String outputName, int flags) throws IOException { 98 if (outputName != null) { 99 try { 100 String parent = new File(outputName).getParent(); 101 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 102 throw new IllegalArgumentException("Optimized data directory " + parent 103 + " is not owned by the current user. Shared storage cannot protect" 104 + " your application from code injection attacks."); 105 } 106 } catch (ErrnoException ignored) { 107 // assume we'll fail with a more contextual error later 108 } 109 } 110 111 mCookie = openDexFile(sourceName, outputName, flags); 112 mFileName = sourceName; 113 guard.open("close"); 114 //System.out.println("DEX FILE cookie is " + mCookie); 115 } 116 117 /** 118 * Open a DEX file, specifying the file in which the optimized DEX 119 * data should be written. If the optimized form exists and appears 120 * to be current, it will be used; if not, the VM will attempt to 121 * regenerate it. 122 * 123 * This is intended for use by applications that wish to download 124 * and execute DEX files outside the usual application installation 125 * mechanism. This function should not be called directly by an 126 * application; instead, use a class loader such as 127 * dalvik.system.DexClassLoader. 128 * 129 * @param sourcePathName 130 * Jar or APK file with "classes.dex". (May expand this to include 131 * "raw DEX" in the future.) 132 * @param outputPathName 133 * File that will hold the optimized form of the DEX data. 134 * @param flags 135 * Enable optional features. (Currently none defined.) 136 * @return 137 * A new or previously-opened DexFile. 138 * @throws IOException 139 * If unable to open the source or output file. 140 */ 141 static public DexFile loadDex(String sourcePathName, String outputPathName, 142 int flags) throws IOException { 143 144 /* 145 * TODO: we may want to cache previously-opened DexFile objects. 146 * The cache would be synchronized with close(). This would help 147 * us avoid mapping the same DEX more than once when an app 148 * decided to open it multiple times. In practice this may not 149 * be a real issue. 150 */ 151 return new DexFile(sourcePathName, outputPathName, flags); 152 } 153 154 /** 155 * Gets the name of the (already opened) DEX file. 156 * 157 * @return the file name 158 */ 159 public String getName() { 160 return mFileName; 161 } 162 163 /** 164 * Closes the DEX file. 165 * <p> 166 * This may not be able to release any resources. If classes from this 167 * DEX file are still resident, the DEX file can't be unmapped. 168 * 169 * @throws IOException 170 * if an I/O error occurs during closing the file, which 171 * normally should not happen 172 */ 173 public void close() throws IOException { 174 if (mCookie != 0) { 175 guard.close(); 176 closeDexFile(mCookie); 177 mCookie = 0; 178 } 179 } 180 181 /** 182 * Loads a class. Returns the class on success, or a {@code null} reference 183 * on failure. 184 * <p> 185 * If you are not calling this from a class loader, this is most likely not 186 * going to do what you want. Use {@link Class#forName(String)} instead. 187 * <p> 188 * The method does not throw {@link ClassNotFoundException} if the class 189 * isn't found because it isn't reasonable to throw exceptions wildly every 190 * time a class is not found in the first DEX file we look at. 191 * 192 * @param name 193 * the class name, which should look like "java/lang/String" 194 * 195 * @param loader 196 * the class loader that tries to load the class (in most cases 197 * the caller of the method 198 * 199 * @return the {@link Class} object representing the class, or {@code null} 200 * if the class cannot be loaded 201 */ 202 public Class loadClass(String name, ClassLoader loader) { 203 String slashName = name.replace('.', '/'); 204 return loadClassBinaryName(slashName, loader, null); 205 } 206 207 /** 208 * See {@link #loadClass(String, ClassLoader)}. 209 * 210 * This takes a "binary" class name to better match ClassLoader semantics. 211 * 212 * @hide 213 */ 214 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { 215 return defineClass(name, loader, mCookie, suppressed); 216 } 217 218 private static Class defineClass(String name, ClassLoader loader, int cookie, 219 List<Throwable> suppressed) { 220 Class result = null; 221 try { 222 result = defineClassNative(name, loader, cookie); 223 } catch (NoClassDefFoundError e) { 224 if (suppressed != null) { 225 suppressed.add(e); 226 } 227 } catch (ClassNotFoundException e) { 228 if (suppressed != null) { 229 suppressed.add(e); 230 } 231 } 232 return result; 233 } 234 235 private static native Class defineClassNative(String name, ClassLoader loader, int cookie) 236 throws ClassNotFoundException, NoClassDefFoundError; 237 238 /** 239 * Enumerate the names of the classes in this DEX file. 240 * 241 * @return an enumeration of names of classes contained in the DEX file, in 242 * the usual internal form (like "java/lang/String"). 243 */ 244 public Enumeration<String> entries() { 245 return new DFEnum(this); 246 } 247 248 /* 249 * Helper class. 250 */ 251 private class DFEnum implements Enumeration<String> { 252 private int mIndex; 253 private String[] mNameList; 254 255 DFEnum(DexFile df) { 256 mIndex = 0; 257 mNameList = getClassNameList(mCookie); 258 } 259 260 public boolean hasMoreElements() { 261 return (mIndex < mNameList.length); 262 } 263 264 public String nextElement() { 265 return mNameList[mIndex++]; 266 } 267 } 268 269 /* return a String array with class names */ 270 native private static String[] getClassNameList(int cookie); 271 272 /** 273 * Called when the class is finalized. Makes sure the DEX file is closed. 274 * 275 * @throws IOException 276 * if an I/O error occurs during closing the file, which 277 * normally should not happen 278 */ 279 @Override protected void finalize() throws Throwable { 280 try { 281 if (guard != null) { 282 guard.warnIfOpen(); 283 } 284 close(); 285 } finally { 286 super.finalize(); 287 } 288 } 289 290 /* 291 * Open a DEX file. The value returned is a magic VM cookie. On 292 * failure, an IOException is thrown. 293 */ 294 private static int openDexFile(String sourceName, String outputName, 295 int flags) throws IOException { 296 return openDexFileNative(new File(sourceName).getCanonicalPath(), 297 (outputName == null) ? null : new File(outputName).getCanonicalPath(), 298 flags); 299 } 300 301 native private static int openDexFileNative(String sourceName, String outputName, 302 int flags) throws IOException; 303 304 /* 305 * Close DEX file. 306 */ 307 native private static void closeDexFile(int cookie); 308 309 /** 310 * Returns true if the VM believes that the apk/jar file is out of date 311 * and should be passed through "dexopt" again. 312 * 313 * @param fileName the absolute path to the apk/jar file to examine. 314 * @return true if dexopt should be called on the file, false otherwise. 315 * @throws java.io.FileNotFoundException if fileName is not readable, 316 * not a file, or not present. 317 * @throws java.io.IOException if fileName is not a valid apk/jar file or 318 * if problems occur while parsing it. 319 * @throws java.lang.NullPointerException if fileName is null. 320 * @throws dalvik.system.StaleDexCacheError if the optimized dex file 321 * is stale but exists on a read-only partition. 322 */ 323 native public static boolean isDexOptNeeded(String fileName) 324 throws FileNotFoundException, IOException; 325}
loadClassBinaryName(name,loader, suppressed)方法直接調用了
---》
defineClass(name, loader, mCookie, suppressed)方法,而該方法 又調用了
---》
private static native Class defineClassNative(String name, ClassLoader loader, int cookie) throws ClassNotFoundException, NoClassDefFoundError;方法
defineClassNative是C或c++的方法
總結:
第一步:執行ClassLoader的loadClass(String className)方法
第二步:執行BaseDexClassLoader的findClass(String classNmae)方法
第三步:執行DexPathList的findClass(String className)方法
第四步:執行DexFile的loadClassBinaryName方法
第五步:執行DexFile的defindClass方法
第六步:C或C++的defineClassNative方法
DexClassLoader加載class的基本流程到此完成。