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