blob: 7051980bb80e6a61773d49aa69a2c5346678cd07 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.ZipFile;
/**
* Provides a simple {@link ClassLoader} implementation that operates on a
* list of jar/apk files with classes.dex entries. The directory that
* holds the optimized form of the files is specified explicitly. This
* can be used to execute code not installed as part of an application.
*
* The best place to put the optimized DEX files is in app-specific
* storage, so that removal of the app will automatically remove the
* optimized DEX files. If other storage is used (e.g. /sdcard), the
* app may not have an opportunity to remove them.
*/
public class DexClassLoader extends ClassLoader {
private static final boolean VERBOSE_DEBUG = false;
/* constructor args, held for init */
private final String mRawDexPath;
private final String mRawLibPath;
private final String mDexOutputPath;
/*
* Parallel arrays for jar/apk files.
*
* (could stuff these into an object and have a single array;
* improves clarity but adds overhead)
*/
private final File[] mFiles; // source file Files, for rsrc URLs
private final ZipFile[] mZips; // source zip files, with resources
private final DexFile[] mDexs; // opened, prepped DEX files
/**
* Native library path.
*/
private final String[] mLibPaths;
/**
* 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.
*
* The path lists are separated using the character specified by
* the "path.separator" system property, which defaults to ":".
*
* @param dexPath
* the list of jar/apk files containing classes and resources
* @param dexOutputDir
* directory where optimized DEX files should be written
* @param libPath
* the list of directories containing native libraries; may be null
* @param parent
* the parent class loader
*/
public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
ClassLoader parent) {
super(parent);
if (dexPath == null || dexOutputDir == null)
throw new NullPointerException();
mRawDexPath = dexPath;
mDexOutputPath = dexOutputDir;
mRawLibPath = libPath;
String[] dexPathList = mRawDexPath.split(":");
int length = dexPathList.length;
//System.out.println("DexClassLoader: " + dexPathList);
mFiles = new File[length];
mZips = new ZipFile[length];
mDexs = new DexFile[length];
/* open all Zip and DEX files up front */
for (int i = 0; i < length; i++) {
//System.out.println("My path is: " + dexPathList[i]);
File pathFile = new File(dexPathList[i]);
mFiles[i] = pathFile;
if (pathFile.isFile()) {
try {
mZips[i] = new ZipFile(pathFile);
} catch (IOException ioex) {
// expecting IOException and ZipException
System.out.println("Failed opening '" + pathFile
+ "': " + ioex);
//ioex.printStackTrace();
}
/* we need both DEX and Zip, because dex has no resources */
try {
String outputName =
generateOutputName(dexPathList[i], mDexOutputPath);
mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
} catch (IOException ioex) {
// might be a resource-only zip
System.out.println("Failed loadDex '" + pathFile
+ "': " + ioex);
}
} else {
if (VERBOSE_DEBUG)
System.out.println("Not found: " + pathFile.getPath());
}
}
/*
* Prep for native library loading.
*/
String pathList = System.getProperty("java.library.path", ".");
String pathSep = System.getProperty("path.separator", ":");
String fileSep = System.getProperty("file.separator", "/");
if (mRawLibPath != null) {
if (pathList.length() > 0) {
pathList += pathSep + mRawLibPath;
}
else {
pathList = mRawLibPath;
}
}
mLibPaths = pathList.split(pathSep);
length = mLibPaths.length;
// Add a '/' to the end so we don't have to do the property lookup
// and concatenation later.
for (int i = 0; i < length; i++) {
if (!mLibPaths[i].endsWith(fileSep))
mLibPaths[i] += fileSep;
if (VERBOSE_DEBUG)
System.out.println("Native lib path " +i+ ": " + mLibPaths[i]);
}
}
/**
* Convert a source path name and an output directory to an output
* file name.
*/
private static String generateOutputName(String sourcePathName,
String outputDir) {
StringBuilder newStr = new StringBuilder(80);
/* start with the output directory */
newStr.append(outputDir);
if (!outputDir.endsWith("/"))
newStr.append("/");
/* get the filename component of the path */
String sourceFileName;
int lastSlash = sourcePathName.lastIndexOf("/");
if (lastSlash < 0)
sourceFileName = sourcePathName;
else
sourceFileName = sourcePathName.substring(lastSlash+1);
/*
* Replace ".jar", ".zip", whatever with ".dex". We don't want to
* use ".odex", because the build system uses that for files that
* are paired with resource-only jar files. If the VM can assume
* that there's no classes.dex in the matching jar, it doesn't need
* to open the jar to check for updated dependencies, providing a
* slight performance boost at startup. The use of ".dex" here
* matches the use on files in /data/dalvik-cache.
*/
int lastDot = sourceFileName.lastIndexOf(".");
if (lastDot < 0)
newStr.append(sourceFileName);
else
newStr.append(sourceFileName, 0, lastDot);
newStr.append(".dex");
if (VERBOSE_DEBUG)
System.out.println("Output file will be " + newStr.toString());
return newStr.toString();
}
/**
* Finds a class. This method is called by {@code loadClass()} after the
* parent ClassLoader has failed to find a loaded class of the same name.
*
* @param name
* The name of the class to search for, in a human-readable form
* like "java.lang.String" or "java.net.URLClassLoader$3$1".
* @return the {@link Class} object representing the class
* @throws ClassNotFoundException
* if the class cannot be found
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (VERBOSE_DEBUG)
System.out.println("DexClassLoader " + this
+ ": findClass '" + name + "'");
int length = mFiles.length;
for (int i = 0; i < length; i++) {
if (VERBOSE_DEBUG)
System.out.println(" Now searching: " + mFiles[i].getPath());
if (mDexs[i] != null) {
String slashName = name.replace('.', '/');
Class clazz = mDexs[i].loadClass(slashName, this);
if (clazz != null) {
if (VERBOSE_DEBUG)
System.out.println(" found");
return clazz;
}
}
}
throw new ClassNotFoundException(name + " in loader " + this);
}
/**
* Finds a resource. This method is called by {@code getResource()} after
* the parent ClassLoader has failed to find a loaded resource of the same
* name.
*
* @param name
* The name of the resource to find
* @return the location of the resource as a URL, or {@code null} if the
* resource is not found.
*/
@Override
protected URL findResource(String name) {
int length = mFiles.length;
for (int i = 0; i < length; i++) {
File pathFile = mFiles[i];
ZipFile zip = mZips[i];
if (zip.getEntry(name) != null) {
if (VERBOSE_DEBUG)
System.out.println(" found " + name + " in " + pathFile);
try {
// File.toURL() is compliant with RFC 1738 in always
// creating absolute path names. If we construct the
// URL by concatenating strings, we might end up with
// illegal URLs for relative names.
return new URL("jar:" + pathFile.toURL() + "!/" + name);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
if (VERBOSE_DEBUG)
System.out.println(" resource " + name + " not found");
return null;
}
/**
* Finds a native library. This method is called after the parent
* ClassLoader has failed to find a native library of the same name.
*
* @param libname
* The name of the library to find
* @return the complete path of the library, or {@code null} if the library
* is not found.
*/
@Override
protected String findLibrary(String libname) {
String fileName = System.mapLibraryName(libname);
for (int i = 0; i < mLibPaths.length; i++) {
String pathName = mLibPaths[i] + fileName;
File test = new File(pathName);
if (test.exists()) {
if (VERBOSE_DEBUG)
System.out.println(" found " + libname);
return pathName;
}
}
if (VERBOSE_DEBUG)
System.out.println(" library " + libname + " not found");
return null;
}
/**
* Returns package information for the given package. Unfortunately, the
* DexClassLoader doesn't really have this information, and as a non-secure
* ClassLoader, it isn't even required to, according to the spec. Yet, we
* want to provide it, in order to make all those hopeful callers of
* <code>myClass.getPackage().getName()</code> happy. Thus we construct a
* Package object the first time it is being requested and fill most of the
* fields with dummy values. The Package object is then put into the
* ClassLoader's Package cache, so we see the same one next time. We don't
* create Package objects for null arguments or for the default package.
* <p>
* There a limited chance that we end up with multiple Package objects
* representing the same package: It can happen when when a package is
* scattered across different JAR files being loaded by different
* ClassLoaders. Rather unlikely, and given that this whole thing is more or
* less a workaround, probably not worth the effort.
*
* @param name
* the name of the class
* @return the package information for the class, or {@code null} if there
* is not package information available for it
*/
@Override
protected Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
synchronized(this) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown",
"Unknown", "0.0", "Unknown", null);
}
return pack;
}
}
return null;
}
}