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