Search code examples
androiddynamicclassloaderdexjmonkeyengine

JMonkey Android custom class loading


I've done a JMonkey app wich exceed 65 000 methods. I want to run it on android. So, I'm using the multi dex option to maven-build the apk. Then I dynamically load the second .dex file.

I stored the second .dex file in a jar, in the apk's assets directory.

This is my android MainActivity code :

public class MainActivityStart extends Activity {

   private static final String SECONDARY_DEX_NAME = "classes2.jar";

/**
 * Buffer size for file copying. While 8kb is used in this sample, you may
 * want to tweak it based on actual size of the secondary dex file involved.
 */
private static final int BUF_SIZE = 8 * 1024;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    loadSecondaryDex();

    Intent intent = new Intent(getApplicationContext(), MainActivity.class);
    startActivity(intent);

}

private void loadSecondaryDex() {
    // Before the secondary dex file can be processed by the DexClassLoader,
    // it has to be first copied from asset resource to a storage location.
    final File dexInternalStoragePath = new File(getDir("dex",
            Context.MODE_PRIVATE), SECONDARY_DEX_NAME);

    prepareDex(dexInternalStoragePath);

    final File dexOutputDir = getDir("outdex", 0);

    DexClassLoader cl = new DexClassLoader(
            dexInternalStoragePath.getAbsolutePath(),
            dexOutputDir.getAbsolutePath(), null, getClassLoader()) {

        @Override
        public Class<?> loadClass(final String className)
                throws ClassNotFoundException {
            return super.loadClass(className);
        }
    };

}

// File I/O code to copy the secondary dex file from asset resource to
// internal storage.
private void prepareDex(final File dexInternalStoragePath) {
    BufferedInputStream bis = null;
    OutputStream dexWriter = null;

    try {
        bis = new BufferedInputStream(getAssets().open(SECONDARY_DEX_NAME));
        dexWriter = new BufferedOutputStream(new FileOutputStream(
                dexInternalStoragePath));
        final byte[] buf = new byte[BUF_SIZE];
        int len;
        while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
            dexWriter.write(buf, 0, len);
        }

        dexWriter.close();
        bis.close();
        // return true;
    } catch (final IOException e) {
        if (dexWriter != null) {
            try {
                dexWriter.close();
            } catch (final IOException ioe) {
                ioe.printStackTrace();
            }
        }
        if (bis != null) {
            try {
                bis.close();
            } catch (final IOException ioe) {
                ioe.printStackTrace();
            }
        }
        // return false;
    }
}

}

As you can see, when the dynamic class loading is done, I switch to the Jmonkey Activity. This one extends the AndroidHarness class adapting JME apps for android.

Here is the code :

public class MainActivity extends AndroidHarness {

public MainActivity() {

    // Set the application class to run
    appClass = "mygame.Main";

    // Try ConfigType.FASTEST; or ConfigType.LEGACY if you have problems
    eglConfigType = ConfigType.LEGACY;

    // Exit Dialog title & message
    exitDialogTitle = "Quit game?";
    exitDialogMessage = "Do you really want to quit the game?";

    // Choose screen orientation
    screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;

    // Invert the MouseEvents X (default = true)
    mouseEventsInvertX = true;

    // Invert the MouseEvents Y (default = true)
    mouseEventsInvertY = true;
  }
}

Running this give me the nice exception :

07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): SEVERE Class mygame.Main init failed 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): java.lang.ClassNotFoundException: mygame.Main 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.Class.classForName(Native Method) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.Class.forName(Class.java:251) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.Class.forName(Class.java:216) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at com.jme3.app.AndroidHarness.onCreate(AndroidHarness.java:223) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.Activity.performCreate(Activity.java:5231) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2148) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2233) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.ActivityThread.access$800(ActivityThread.java:135) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.os.Handler.dispatchMessage(Handler.java:102) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.os.Looper.loop(Looper.java:136) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at android.app.ActivityThread.main(ActivityThread.java:5001) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.reflect.Method.invokeNative(Native Method) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.reflect.Method.invoke(Method.java:515) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at dalvik.system.NativeStart.main(Native Method) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): Caused by: java.lang.NoClassDefFoundError: mygame/Main 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): ... 18 more 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): Caused by: java.lang.ClassNotFoundException: Didn't find class "mygame.Main" on path: DexPathList[[zip file "/data/app/com.example.j4a-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.example.j4a-1, /vendor/lib, /system/lib]] 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.ClassLoader.loadClass(ClassLoader.java:497) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): at java.lang.ClassLoader.loadClass(ClassLoader.java:457) 07-28 10:59:21.645: E/com.jme3.app.AndroidHarness(3788): ... 18 more

So, the main class is not found ... Do you guys have any idea to do the trick ?

One extra questions : I can't access methods provided by Activity class directly within the MainActivity class extending AndroidHarness, such as getDir(); getAssets(); or more important, getContext(); It will always return an NPE ...

If I could do so, I would be able to do the dynamic loading in this class and call the mygame.Main class by reflection, which could be great. So if you guys have the solution ... thank you by advance !


Solution

  • Your problem seems to be linked to the ClassLoader.

    When you call Class.forName(MyGame) the VM tries to find the class from the calling ClassLoader (the ClassLoader associated with the class containing to call to Class.forName). According to your post, I think that your class has either not been loaded yet, or has been loaded by another ClassLoader.

    Before calling Class.forName, you must ensure that the class you are looking for has been loaded, and has been loaded by the same ClassLoader as the one which loaded the calling class or by one of its parents.