summaryrefslogtreecommitdiffstats
path: root/dalvik/src/main/java/dalvik/system/DexClassLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'dalvik/src/main/java/dalvik/system/DexClassLoader.java')
-rw-r--r--dalvik/src/main/java/dalvik/system/DexClassLoader.java362
1 files changed, 362 insertions, 0 deletions
diff --git a/dalvik/src/main/java/dalvik/system/DexClassLoader.java b/dalvik/src/main/java/dalvik/system/DexClassLoader.java
new file mode 100644
index 0000000..cb2f76d
--- /dev/null
+++ b/dalvik/src/main/java/dalvik/system/DexClassLoader.java
@@ -0,0 +1,362 @@
+/*
+ * 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.URL;
+import java.util.zip.ZipFile;
+import java.net.MalformedURLException;
+
+import dalvik.system.DexFile;
+
+/**
+ * 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;
+
+ private boolean mInitialized;
+
+ /*
+ * Parallel arrays for jar/apk files.
+ *
+ * (could stuff these into an object and have a single array;
+ * improves clarity but adds overhead)
+ */
+ private File[] mFiles; // source file Files, for rsrc URLs
+ private ZipFile[] mZips; // source zip files, with resources
+ private DexFile[] mDexs; // opened, prepped DEX files
+
+ /*
+ * Native library path.
+ */
+ private 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;
+ }
+
+ /**
+ * Complete initialization on first use of the class loader.
+ */
+ private void ensureInit() {
+ if (mInitialized) {
+ return;
+ }
+
+ String[] dexPathList;
+
+ mInitialized = true;
+
+ 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 ".odex" */
+ int lastDot = sourceFileName.lastIndexOf(".");
+ if (lastDot < 0)
+ newStr.append(sourceFileName);
+ else
+ newStr.append(sourceFileName, 0, lastDot);
+ newStr.append(".odex");
+
+ 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 {
+ ensureInit();
+
+ if (VERBOSE_DEBUG)
+ System.out.println("DexClassLoader " + this
+ + ": findClass '" + name + "'");
+
+ byte[] data = null;
+ 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) {
+ ensureInit();
+
+ 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) {
+ ensureInit();
+
+ 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 && !"".equals(name)) {
+ 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;
+ }
+}
+