Wait what
Wait what

Reputation: 90

How to load a class from a byte array in android?

First off I have seen Load Java-Byte-Code at Runtime and it was helpful in so far as getting to me to the same spot I'm stuck at currently.

I'm trying to load a class from a byte array to avoid storing a file on disk. For testing purpose in this example I am simply reading in a .class file into a byte array so obviously the file is still stored on disk, but it is just to see if the code can work.

I take this byte array and then use a custom ClassLoader with the method loadClass to load a Class, but it doesn't work.

    byte[] bytearray = null;
    try{    
    RandomAccessFile f = new RandomAccessFile("/sdcard/ClassToGet.dex", "r");
    bytearray = new byte[(int) f.length()];
    f.read(bytearray);

    MyClassLoader classloader = new MyClassLoader();
    classloader.setBuffer(bytearray); 
    classloader.loadClass("com.pack.ClassIWant");
    } 

Here is the ClassLoader implementation:

public class MyClassLoader extends DexClassLoader {

 private byte[] buffer;

  @Override
    public Class findClass(String className){
    byte[] b = getBuffer();
    return this.defineClass(className, b, 0, b.length);
    }

public void setBuffer(byte[] b){
    buffer = b;
}
public byte[] getBuffer(){
    return buffer;
}

And the error I am receiving is this:

java.lang.UnsupportedOperationException: can't load this type of class file at java.lang.VMClassLoader.defineClass(Native Method)

I have supplied it with .class files, .dex files, .apk, .jar, etc... I have no idea what "type of class file" it wants from me and the documentation on it is nonexistent. Any help would be great I've been trying to get this work for four days straight.

Upvotes: 5

Views: 7881

Answers (3)

user1417332
user1417332

Reputation: 51

I have the same problem that you have.

The reason why I get the error "can't load this type of class file" is simple.

In the platform source, "/dalvik/vm/native/java_lang_VMClassLoader.cpp" which is related to the method "defineClass" always returns exception as follows. (version: ICS)

Finally, I reached the conclusion that I can't load .dex in byte array format.

Is there anyone who can load .dex by using byte array? (not using files)

/*
 * static Class defineClass(ClassLoader cl, String name,
 *     byte[] data, int offset, int len)
 *     throws ClassFormatError
 *
 * Convert an array of bytes to a Class object.
 */
static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args, JValue* pResult)
{
    Object* loader = (Object*) args[0];
    StringObject* nameObj = (StringObject*) args[1];
    const u1* data = (const u1*) args[2];
    int offset = args[3];
    int len = args[4];
    char* name = NULL;

    name = dvmCreateCstrFromString(nameObj);
    ALOGE("ERROR: defineClass(%p, %s, %p, %d, %d)",
        loader, name, data, offset, len);
    dvmThrowUnsupportedOperationException(
        "can't load this type of class file");

    free(name);
    RETURN_VOID();
}

/*
 * static Class defineClass(ClassLoader cl, byte[] data, int offset,
 *     int len)
 *     throws ClassFormatError
 *
 * Convert an array of bytes to a Class object. Deprecated version of
 * previous method, lacks name parameter.
 */
static void Dalvik_java_lang_VMClassLoader_defineClass2(const u4* args, JValue* pResult)
{
    Object* loader = (Object*) args[0];
    const u1* data = (const u1*) args[1];
    int offset = args[2];
    int len = args[3];

    ALOGE("ERROR: defineClass(%p, %p, %d, %d)",
        loader, data, offset, len);
    dvmThrowUnsupportedOperationException(
        "can't load this type of class file");

    RETURN_VOID();
}

Upvotes: 2

Jesse Wilson
Jesse Wilson

Reputation: 40623

Make sure your .dex file is a genuine dx-produced Dalvik executable and not a Java .class file in disguise. If you use the .dex extension the file must be a .dex file; otherwise use a .jar extension for a ZIP file that contains a classes.dex entry.

Not all versions of Dalvik can load classes from memory. You can work around this by loading the class from the file system. There's an example in DexMaker's generateAndLoad method:

    byte[] dex = ...

    /*
     * This implementation currently dumps the dex to the filesystem. It
     * jars the emitted .dex for the benefit of Gingerbread and earlier
     * devices, which can't load .dex files directly.
     *
     * TODO: load the dex from memory where supported.
     */
    File result = File.createTempFile("Generated", ".jar", dexCache);
    result.deleteOnExit();
    JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
    jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
    jarOut.write(dex);
    jarOut.closeEntry();
    jarOut.close();
    try {
        return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
                .getConstructor(String.class, String.class, String.class, ClassLoader.class)
                .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent);
    } catch (ClassNotFoundException e) {
        throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e.getCause());
    } catch (InstantiationException e) {
        throw new AssertionError();
    } catch (NoSuchMethodException e) {
        throw new AssertionError();
    } catch (IllegalAccessException e) {
        throw new AssertionError();
    }

Upvotes: 1

tolgap
tolgap

Reputation: 9778

Android does not run JVM bytecode, but Dalvik bytecode. So your operation should include this line before defineClass()

context.setOptimizationLevel(-1);

Upvotes: 0

Related Questions