Mercurial > hg > icedrobot > daneel
changeset 92:eafd801c8fa0
Refactor DaneelClassLoader to load DEX file lazily when a class is requested instead in the constructor.
This patch also loads APK lazily when getResource is called.
author | forax |
---|---|
date | Mon, 28 Mar 2011 00:45:02 +0200 |
parents | 2711ac37fdfb |
children | 52fb46737ce6 |
files | src/main/java/org/icedrobot/daneel/dex/DexFile.java src/main/java/org/icedrobot/daneel/loader/ApkFile.java src/main/java/org/icedrobot/daneel/loader/DaneelClassLoader.java |
diffstat | 3 files changed, 139 insertions(+), 53 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/java/org/icedrobot/daneel/dex/DexFile.java Mon Mar 28 00:29:55 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/dex/DexFile.java Mon Mar 28 00:45:02 2011 +0200 @@ -39,10 +39,13 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; import java.util.LinkedHashMap; import java.util.Map; @@ -58,16 +61,53 @@ /** Constant used to indicate that an index value is absent. */ public static final int NO_INDEX = 0xffffffff; - public static DexFile parse(ByteBuffer buf) { - return new DexFile(buf); + + /** + * Create a DEX file from a buffer of bytes. + * + * @param buffer a buffer of bytes containing classes encoded using the DEX format. + * @return a DEX buffer + */ + public static DexFile parse(ByteBuffer buffer) { + return new DexFile(buffer); } + /** + * Create a DEX file from a file by reading its content. + * The file should be encoded using the DEX file format. + * + * @param file a file. + * @return a DEX file + * @throws IOException if the file can't be read. + */ public static DexFile parse(File file) throws IOException { ByteBuffer buffer = (new RandomAccessFile(file, "r")).getChannel().map( FileChannel.MapMode.READ_ONLY, 0, file.length()); return new DexFile(buffer); } + /** + * Create a DEX file from an input stream and a size. + * The input stream should be encoded using the DEX file format. + * + * @param inputStream an input stream + * @param size the size of the content of the input stream. + * @return a DEX file + * @throws IOException if the content of the input stream can't be read. + */ + public static DexFile parse(InputStream inputStream, long size) throws IOException { + if (size > Integer.MAX_VALUE) + throw new IllegalArgumentException("Oversized DEX file detected."); + + ByteBuffer buffer = ByteBuffer.allocate((int)size); + ReadableByteChannel channel = Channels.newChannel(inputStream); + while (buffer.hasRemaining()) { + channel.read(buffer); + } + buffer.clear(); + return new DexFile(buffer); + } + public static DexFile parse(byte[] bytes) { return new DexFile(ByteBuffer.wrap(bytes)); }
--- a/src/main/java/org/icedrobot/daneel/loader/ApkFile.java Mon Mar 28 00:29:55 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/loader/ApkFile.java Mon Mar 28 00:45:02 2011 +0200 @@ -41,8 +41,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; import java.util.jar.JarFile; import java.util.zip.ZipEntry; @@ -83,17 +81,14 @@ * Returns the DEX file containing all the classes of this APK file. * @return The contained DEX file object. * @throws IOException In case of an error while accessing the file. + * @throws IllegalStateException if the APK doesn't contains any classes. */ - public DexFile getClasses() throws IOException { + public DexFile getDexFile() throws IOException { ZipEntry entry = getEntry("classes.dex"); if (entry == null) - throw new DaneelException("The APK doesn't contain classes."); - if (entry.getSize() > Integer.MAX_VALUE) - throw new DaneelException("Oversized DEX file detected."); - ByteBuffer buffer = ByteBuffer.allocate((int) entry.getSize()); - Channels.newChannel(getInputStream(entry)).read(buffer); - buffer.rewind(); - return DexFile.parse(buffer); + throw new IllegalStateException("The APK doesn't contain classes."); + + return DexFile.parse(getInputStream(entry), entry.getSize()); } /**
--- a/src/main/java/org/icedrobot/daneel/loader/DaneelClassLoader.java Mon Mar 28 00:29:55 2011 +0200 +++ b/src/main/java/org/icedrobot/daneel/loader/DaneelClassLoader.java Mon Mar 28 00:45:02 2011 +0200 @@ -38,13 +38,12 @@ package org.icedrobot.daneel.loader; import java.io.File; +import java.io.IOError; import java.io.IOException; import java.net.URL; -import java.util.LinkedList; -import java.util.List; +import java.util.ArrayList; import java.util.zip.ZipEntry; -import org.icedrobot.daneel.DaneelException; import org.icedrobot.daneel.dex.DexFile; import org.icedrobot.daneel.rewriter.DexRewriter; @@ -54,12 +53,19 @@ * and then defining them in the host VM itself. */ public class DaneelClassLoader extends ClassLoader { + private final File[] files; - /** The list of all files containing classes. */ - private final DexFile[] classFiles; - - /** The list of all files containing resources. */ - private final ApkFile[] resourceFiles; + /** The list of all files containing DEX classes (lazy initialized). */ + private DexFile[] dexFiles; + + /** Lock used to lazy initialize dexFiles */ + private final Object dexLock = new Object(); + + /** The list of all files containing resources (lazy initialized). */ + private ApkFile[] resourceFiles; + + /** Lock used to lazy initialize resourceFiles */ + private final Object resourcesLock = new Object(); /** * Constructs a new class loader. Keep a constructor with such a signature @@ -68,8 +74,9 @@ * documentation. * * @param parent The parent class loader for delegation. + * @throws IOException throws if an I/O error occurs while reading a DEX files */ - public DaneelClassLoader(ClassLoader parent) { + public DaneelClassLoader(ClassLoader parent) throws IOException { this(parent, defaultFiles()); } @@ -79,43 +86,37 @@ * * @param parent The parent class loader for delegation. * @param files The set of files from which to load. + * @throws IOException throws if an I/O error occurs while reading a DEX files */ - public DaneelClassLoader(ClassLoader parent, File[] files) { + public DaneelClassLoader(ClassLoader parent, File... files) throws IOException { super(parent); + this.files = files.clone(); + } - // Split the given files into APK and DEX files. - List<DexFile> dexs = new LinkedList<DexFile>(); - List<ApkFile> apks = new LinkedList<ApkFile>(); - for (File file : files) { - try { - if (file.getName().endsWith(".apk")) { - ApkFile apk = new ApkFile(file); - dexs.add(apk.getClasses()); - apks.add(apk); - } else { - DexFile dex = DexFile.parse(file); - dexs.add(dex); + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + System.out.printf("Trying to find class '%s' ...\n", name); + + DexFile[] dexFiles; + synchronized(dexLock) { + dexFiles = this.dexFiles; + if (dexFiles == null) { + // avoid an infinite loop if loadDexFiles() requires to load a class + // that isn't available in parent classloaders + this.dexFiles = new DexFile[0]; + + try { + this.dexFiles = dexFiles = loadDexFiles(files); + } catch (IOException e) { + throw new IOError(e); } - } catch (IOException e) { - throw new DaneelException("Unable to setup class loader.", e); } } - classFiles = dexs.toArray(new DexFile[dexs.size()]); - resourceFiles = apks.toArray(new ApkFile[apks.size()]); - } - - /** - * Method which tries to find a class for the given name. - */ - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - System.out.printf("Trying to find class '%s' ...\n", name); - - // Iterate over all files containing classes. - for (DexFile dex : classFiles) { + // Iterate over all dex files containing classes. + for (DexFile dexFile : dexFiles) { try { - byte[] bytecode = DexRewriter.rewrite(name, dex); + byte[] bytecode = DexRewriter.rewrite(name, dexFile); return defineClass(name, bytecode, 0, bytecode.length); } catch (ClassNotFoundException e) { // Ignore and skip to the next file. @@ -126,12 +127,24 @@ throw new ClassNotFoundException(name); } - /** - * Method which tries to find a resource for the given name. - */ @Override protected URL findResource(String name) { System.out.printf("Trying to find resource '%s' ...\n", name); + + ApkFile[] resourceFiles; + synchronized(resourcesLock) { + resourceFiles = this.resourceFiles; + if (resourceFiles == null) { + // avoid an infinite loop if findResourceFiles() calls + // findResource() on the current classloader + this.resourceFiles = new ApkFile[0]; + try { + this.resourceFiles = resourceFiles = findResourceFiles(files); + } catch (IOException e) { + throw new IOError(e); + } + } + } // Iterate over all files containing resources. for (ApkFile apk : resourceFiles) { @@ -157,4 +170,42 @@ files[i] = new File(paths[i]); return files; } + + /** + * Filter an array of files to find all DEX files. + * @param files an array of files. + * @return an array of DEX files. + * @throws IOException + */ + private static DexFile[] loadDexFiles(File[] files) throws IOException { + ArrayList<DexFile> dexs = new ArrayList<DexFile>(files.length); + for (File file : files) { + DexFile dexFile; + if (file.getName().endsWith(".apk")) { + ApkFile apk = new ApkFile(file); + dexFile = apk.getDexFile(); + } else { + dexFile = DexFile.parse(file); + } + dexs.add(dexFile); + } + return dexs.toArray(new DexFile[dexs.size()]); + } + + /** + * Filter an array of files to find all APK files. + * @param files an array of files. + * @return an array of APK files. + * @throws IOException + */ + private static ApkFile[] findResourceFiles(File[] files) throws IOException { + ArrayList<ApkFile> apks = new ArrayList<ApkFile>(files.length); + for (File file : files) { + if (file.getName().endsWith(".apk")) { + ApkFile apk = new ApkFile(file); + apks.add(apk); + } + } + return apks.toArray(new ApkFile[apks.size()]); + } }