DexClassLoader.java revision 80a7fbab52b96c9fd47c72f8987d1babe2cd001d
1/* 2 * Copyright (C) 2008 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.URL; 22import java.util.zip.ZipFile; 23import java.net.MalformedURLException; 24 25import dalvik.system.DexFile; 26 27/** 28 * Provides a simple {@link ClassLoader} implementation that operates on a 29 * list of jar/apk files with classes.dex entries. The directory that 30 * holds the optimized form of the files is specified explicitly. This 31 * can be used to execute code not installed as part of an application. 32 * 33 * The best place to put the optimized DEX files is in app-specific 34 * storage, so that removal of the app will automatically remove the 35 * optimized DEX files. If other storage is used (e.g. /sdcard), the 36 * app may not have an opportunity to remove them. 37 */ 38public class DexClassLoader extends ClassLoader { 39 40 private static final boolean VERBOSE_DEBUG = false; 41 42 /* constructor args, held for init */ 43 private final String mRawDexPath; 44 private final String mRawLibPath; 45 private final String mDexOutputPath; 46 47 private boolean mInitialized; 48 49 /* 50 * Parallel arrays for jar/apk files. 51 * 52 * (could stuff these into an object and have a single array; 53 * improves clarity but adds overhead) 54 */ 55 private File[] mFiles; // source file Files, for rsrc URLs 56 private ZipFile[] mZips; // source zip files, with resources 57 private DexFile[] mDexs; // opened, prepped DEX files 58 59 /* 60 * Native library path. 61 */ 62 private String[] mLibPaths; 63 64 65 /** 66 * Creates a {@code DexClassLoader} that finds interpreted and native 67 * code. Interpreted classes are found in a set of DEX files contained 68 * in Jar or APK files. 69 * 70 * The path lists are separated using the character specified by 71 * the "path.separator" system property, which defaults to ":". 72 * 73 * @param dexPath 74 * the list of jar/apk files containing classes and resources 75 * @param dexOutputDir 76 * directory where optimized DEX files should be written 77 * @param libPath 78 * the list of directories containing native libraries; may be null 79 * @param parent 80 * the parent class loader 81 */ 82 public DexClassLoader(String dexPath, String dexOutputDir, String libPath, 83 ClassLoader parent) { 84 85 super(parent); 86 87 if (dexPath == null || dexOutputDir == null) 88 throw new NullPointerException(); 89 90 mRawDexPath = dexPath; 91 mDexOutputPath = dexOutputDir; 92 mRawLibPath = libPath; 93 } 94 95 /** 96 * Complete initialization on first use of the class loader. 97 */ 98 private synchronized void ensureInit() { 99 if (mInitialized) { 100 return; 101 } 102 103 String[] dexPathList; 104 105 mInitialized = true; 106 107 dexPathList = mRawDexPath.split(":"); 108 int length = dexPathList.length; 109 110 //System.out.println("DexClassLoader: " + dexPathList); 111 mFiles = new File[length]; 112 mZips = new ZipFile[length]; 113 mDexs = new DexFile[length]; 114 115 /* open all Zip and DEX files up front */ 116 for (int i = 0; i < length; i++) { 117 //System.out.println("My path is: " + dexPathList[i]); 118 File pathFile = new File(dexPathList[i]); 119 mFiles[i] = pathFile; 120 121 if (pathFile.isFile()) { 122 try { 123 mZips[i] = new ZipFile(pathFile); 124 } catch (IOException ioex) { 125 // expecting IOException and ZipException 126 System.out.println("Failed opening '" + pathFile 127 + "': " + ioex); 128 //ioex.printStackTrace(); 129 } 130 131 /* we need both DEX and Zip, because dex has no resources */ 132 try { 133 String outputName = 134 generateOutputName(dexPathList[i], mDexOutputPath); 135 mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0); 136 } catch (IOException ioex) { 137 // might be a resource-only zip 138 System.out.println("Failed loadDex '" + pathFile 139 + "': " + ioex); 140 } 141 } else { 142 if (VERBOSE_DEBUG) 143 System.out.println("Not found: " + pathFile.getPath()); 144 } 145 } 146 147 /* 148 * Prep for native library loading. 149 */ 150 String pathList = System.getProperty("java.library.path", "."); 151 String pathSep = System.getProperty("path.separator", ":"); 152 String fileSep = System.getProperty("file.separator", "/"); 153 154 if (mRawLibPath != null) { 155 if (pathList.length() > 0) { 156 pathList += pathSep + mRawLibPath; 157 } 158 else { 159 pathList = mRawLibPath; 160 } 161 } 162 163 mLibPaths = pathList.split(pathSep); 164 length = mLibPaths.length; 165 166 // Add a '/' to the end so we don't have to do the property lookup 167 // and concatenation later. 168 for (int i = 0; i < length; i++) { 169 if (!mLibPaths[i].endsWith(fileSep)) 170 mLibPaths[i] += fileSep; 171 if (VERBOSE_DEBUG) 172 System.out.println("Native lib path " +i+ ": " + mLibPaths[i]); 173 } 174 } 175 176 /** 177 * Convert a source path name and an output directory to an output 178 * file name. 179 */ 180 private static String generateOutputName(String sourcePathName, 181 String outputDir) { 182 183 StringBuilder newStr = new StringBuilder(80); 184 185 /* start with the output directory */ 186 newStr.append(outputDir); 187 if (!outputDir.endsWith("/")) 188 newStr.append("/"); 189 190 /* get the filename component of the path */ 191 String sourceFileName; 192 int lastSlash = sourcePathName.lastIndexOf("/"); 193 if (lastSlash < 0) 194 sourceFileName = sourcePathName; 195 else 196 sourceFileName = sourcePathName.substring(lastSlash+1); 197 198 /* 199 * Replace ".jar", ".zip", whatever with ".dex". We don't want to 200 * use ".odex", because the build system uses that for files that 201 * are paired with resource-only jar files. If the VM can assume 202 * that there's no classes.dex in the matching jar, it doesn't need 203 * to open the jar to check for updated dependencies, providing a 204 * slight performance boost at startup. The use of ".dex" here 205 * matches the use on files in /data/dalvik-cache. 206 */ 207 int lastDot = sourceFileName.lastIndexOf("."); 208 if (lastDot < 0) 209 newStr.append(sourceFileName); 210 else 211 newStr.append(sourceFileName, 0, lastDot); 212 newStr.append(".dex"); 213 214 if (VERBOSE_DEBUG) 215 System.out.println("Output file will be " + newStr.toString()); 216 return newStr.toString(); 217 } 218 219 /** 220 * Finds a class. This method is called by {@code loadClass()} after the 221 * parent ClassLoader has failed to find a loaded class of the same name. 222 * 223 * @param name 224 * The name of the class to search for, in a human-readable form 225 * like "java.lang.String" or "java.net.URLClassLoader$3$1". 226 * @return the {@link Class} object representing the class 227 * @throws ClassNotFoundException 228 * if the class cannot be found 229 */ 230 @Override 231 protected Class<?> findClass(String name) throws ClassNotFoundException { 232 ensureInit(); 233 234 if (VERBOSE_DEBUG) 235 System.out.println("DexClassLoader " + this 236 + ": findClass '" + name + "'"); 237 238 int length = mFiles.length; 239 240 for (int i = 0; i < length; i++) { 241 if (VERBOSE_DEBUG) 242 System.out.println(" Now searching: " + mFiles[i].getPath()); 243 244 if (mDexs[i] != null) { 245 String slashName = name.replace('.', '/'); 246 Class clazz = mDexs[i].loadClass(slashName, this); 247 if (clazz != null) { 248 if (VERBOSE_DEBUG) 249 System.out.println(" found"); 250 return clazz; 251 } 252 } 253 } 254 255 throw new ClassNotFoundException(name + " in loader " + this); 256 } 257 258 /** 259 * Finds a resource. This method is called by {@code getResource()} after 260 * the parent ClassLoader has failed to find a loaded resource of the same 261 * name. 262 * 263 * @param name 264 * The name of the resource to find 265 * @return the location of the resource as a URL, or {@code null} if the 266 * resource is not found. 267 */ 268 @Override 269 protected URL findResource(String name) { 270 ensureInit(); 271 272 int length = mFiles.length; 273 274 for (int i = 0; i < length; i++) { 275 File pathFile = mFiles[i]; 276 ZipFile zip = mZips[i]; 277 278 if (zip.getEntry(name) != null) { 279 if (VERBOSE_DEBUG) 280 System.out.println(" found " + name + " in " + pathFile); 281 try { 282 // File.toURL() is compliant with RFC 1738 in always 283 // creating absolute path names. If we construct the 284 // URL by concatenating strings, we might end up with 285 // illegal URLs for relative names. 286 return new URL("jar:" + pathFile.toURL() + "!/" + name); 287 } catch (MalformedURLException e) { 288 throw new RuntimeException(e); 289 } 290 } 291 } 292 293 if (VERBOSE_DEBUG) 294 System.out.println(" resource " + name + " not found"); 295 296 return null; 297 } 298 299 /** 300 * Finds a native library. This method is called after the parent 301 * ClassLoader has failed to find a native library of the same name. 302 * 303 * @param libname 304 * The name of the library to find 305 * @return the complete path of the library, or {@code null} if the library 306 * is not found. 307 */ 308 @Override 309 protected String findLibrary(String libname) { 310 ensureInit(); 311 312 String fileName = System.mapLibraryName(libname); 313 for (int i = 0; i < mLibPaths.length; i++) { 314 String pathName = mLibPaths[i] + fileName; 315 File test = new File(pathName); 316 317 if (test.exists()) { 318 if (VERBOSE_DEBUG) 319 System.out.println(" found " + libname); 320 return pathName; 321 } 322 } 323 324 if (VERBOSE_DEBUG) 325 System.out.println(" library " + libname + " not found"); 326 return null; 327 } 328 329 /** 330 * Returns package information for the given package. Unfortunately, the 331 * DexClassLoader doesn't really have this information, and as a non-secure 332 * ClassLoader, it isn't even required to, according to the spec. Yet, we 333 * want to provide it, in order to make all those hopeful callers of 334 * <code>myClass.getPackage().getName()</code> happy. Thus we construct a 335 * Package object the first time it is being requested and fill most of the 336 * fields with dummy values. The Package object is then put into the 337 * ClassLoader's Package cache, so we see the same one next time. We don't 338 * create Package objects for null arguments or for the default package. 339 * <p> 340 * There a limited chance that we end up with multiple Package objects 341 * representing the same package: It can happen when when a package is 342 * scattered across different JAR files being loaded by different 343 * ClassLoaders. Rather unlikely, and given that this whole thing is more or 344 * less a workaround, probably not worth the effort. 345 * 346 * @param name 347 * the name of the class 348 * @return the package information for the class, or {@code null} if there 349 * is not package information available for it 350 */ 351 @Override 352 protected Package getPackage(String name) { 353 if (name != null && !name.isEmpty()) { 354 synchronized(this) { 355 Package pack = super.getPackage(name); 356 357 if (pack == null) { 358 pack = definePackage(name, "Unknown", "0.0", "Unknown", 359 "Unknown", "0.0", "Unknown", null); 360 } 361 362 return pack; 363 } 364 } 365 366 return null; 367 } 368} 369