FileDocCategorySizeDatePackage
DexClassLoader.javaAPI DocAndroid 1.5 API13105Wed May 06 22:41:02 BST 2009dalvik.system

DexClassLoader

public class DexClassLoader extends ClassLoader
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.

Fields Summary
private static final boolean
VERBOSE_DEBUG
private final String
mRawDexPath
private final String
mRawLibPath
private final String
mDexOutputPath
private boolean
mInitialized
private File[]
mFiles
private ZipFile[]
mZips
private dalvik.system.DexFile[]
mDexs
private String[]
mLibPaths
Constructors Summary
public DexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent)
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



                                                                                                 
          
          

        super(parent);

        if (dexPath == null || dexOutputDir == null)
            throw new NullPointerException();

        mRawDexPath = dexPath;
        mDexOutputPath = dexOutputDir;
        mRawLibPath = libPath;
    
Methods Summary
private synchronized voidensureInit()
Complete initialization on first use of the class loader.

        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]);
        }
    
protected java.lang.ClassfindClass(java.lang.String name)
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

        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);
    
protected java.lang.StringfindLibrary(java.lang.String libname)
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.

        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;
    
protected java.net.URLfindResource(java.lang.String name)
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.

        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;
    
private static java.lang.StringgenerateOutputName(java.lang.String sourcePathName, java.lang.String outputDir)
Convert a source path name and an output directory to an output file name.


        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();
    
protected java.lang.PackagegetPackage(java.lang.String name)
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 myClass.getPackage().getName() 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.

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

        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;