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