3

Intro: I'm trying to load code in run-time from a classes.dex that's inside a classes.jar. I've done a lot of research and spend a lot of time on this so I really need help.

Problem: When my jar contains a simple class it is loaded successfully. However, in the exact same situation when my jar contains the same class, only now that class implements an interface, loading this class fails with error:

"Didn't find class "com.x.y.z.w.Patch" on path: DexPathList[[zip file "/data/user/0/x.y.z/app_dex/classes_dexed.jar"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]"

I also get:

I/dalvikvm: Failed resolving Lcom/x/y/TestClassImpl; interface 4 'Lcom/x/y/TestClassInterface;'

Environment: - Functions used to try loading a class:

(DexClassLoader instance).loadClass & (DexFile instance).loadClass.

Specifically:

Method 1:

DexFile dx = DexFile.loadDex(jarInternalPath, File.createTempFile("opt", "dex", 
    context.getCacheDir()).getPath(), 0);
Class<?> targetClass = dx.loadClass(className, 
    ClassLoader.getSystemClassLoader()); // Error

Method 2:

DexClassLoader dexClassLoader = new DexClassLoader(jarInternalPath,
    dexOutPath.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
Class<?> targetClass = dexClassLoader.loadClass(className); // Error
  • IDE: Android Studio 2.1.2
  • Test devices: Emulator Nexus 4 (with APIs 16, 19 & 23), also Physical Nexus 5 Device with Android 6.0.1
  • Most Surprising thing: When I print the classes of the dex file I see the correct class path. I use DexFile to load the dex content and print its classes paths. This method recognizes the correct content! It makes me think there's something to do with either Android permissions or Linux permissions. I don't know..

This is my print method:

try {
    DexFile dx = DexFile.loadDex(jarInternalPath, File.createTempFile("opt", "dex",
        context.getCacheDir()).getPath(), 0);

    // Print all classes in the DexFile
    for (Enumeration<String> classNames = dx.entries(); classNames.hasMoreElements();) {
        String className = classNames.nextElement();
        Log.d(TAG, "Analyzing dex content, fonud class: " + className);
    }
} catch (IOException e) {
    Log.d(TAG, "Error opening " + jarInternalPath, e);
}
  • P.S. Seems irrelevant but maybe it's not: My Host app, the common interface and the implementation of the new content all sit in 3 different modules of the same project. The app has package name of x.y.z, the interface has pkgName of x.y.z.interface and the implementation has pkgName of x.y.z.impl.

A second project I tried with all 3 modules in the same, 1 module failed to work the same way.

Please, help!

Mike Lowery
  • 2,630
  • 4
  • 34
  • 44
esWoobi
  • 31
  • 1
  • 5
  • try something like this ... https://gist.github.com/SelvinPL/eefa88add4fa33f41ca8 ... you see, i'm overriding the `getClassLoader` of the Activity ... why? because i'm loading multiple jars and if you would not do this you would create the new instance of loader for every load jar .... instead overriding it in Activity you may: override it in Application class or do some static global loader – Selvin Jun 29 '16 at 13:45
  • are you using gradle? how you adding dependencies (compile/provided)... why are you have 3 packages (apk and 2 jar)? all interfaces(used by apk itself) should be compiled into apk (as you could not use 'em in would be loaded dynamically) ... are you having any cilcular dependencies ? – Selvin Jun 29 '16 at 14:03
  • Hey Selvin, answering your 1st comment, i'm not worried about multiple loader instances, yet. As for 2nd comment, I'm using Gradle, 3 modules: the app, the interface & the implementation. The interface is verified to be compiled into the apk. 'app' module depends on interface module, interface has no dependencies, implementation also depends on interface but isn't compiled when 'app' module is compiled into an apk (since i manually compile it, dex it and put it in the assets folder of the 'app' module for local test). – esWoobi Jun 29 '16 at 14:16
  • as i wrote you have to use `provided` instead `compile` in the dependencies for impl modules – Selvin Jun 29 '16 at 15:15
  • https://github.com/SelvinPL/android_loaderapp ... http://selvin/loaderappimplX.jar is just an unsigned apk from implX build **most important thing is a build.gradle** in implX and app module ... also as you can see interface is a java module – Selvin Jun 29 '16 at 15:29
  • First of all Selvin, your help is much appreciated, thank you. However it doesn't answer my question. How can it be that my described work can load a jar file but can not load the same jar file if its content class extends an interface. I will try and understand why your implementation can load interface implementations and why mine can't (perhaps it's because my test tries to load them from the assets folder which has read only permissions). One last question, how did you produce the loaderappimplX jars with a manifest inside them? – esWoobi Jun 30 '16 at 08:11
  • Because in your implementation interface is defined twice in apk and in impl.jar ... system cannot load the same class/interface twice... *how did you produce the loaderappimplX jars with a manifest inside them?* as i said it is just renamed build\outputs\apk\impl2-release-unsigned.apk – Selvin Jun 30 '16 at 08:13
  • "interface is defined twice", it's not true. Same test using 'provided' dependency between impl and interface resulted in same error. It fails loading the class. Why is your interface defined as Java? – esWoobi Jun 30 '16 at 08:29
  • It because, AFAIK, you can not add dependencies for android library as `provided` – Selvin Jun 30 '16 at 09:00
  • Selvin thanks a lot. I cannot mark these comments as an answer as my original answer isn't answered yet but i really appreciate your help. – esWoobi Jun 30 '16 at 09:18

1 Answers1

0

that's not enough for load your class. you must change system class loader for use your own class from dex dynamically and programmatically. if not you can't use your interface or class or whatever.

if you want load class dynamically, load class from dexfile or dexclassloader first, and use that.

example,

Class<?> myOwnClass = MyDexClassLoader.loadClass("com.example.my");
hanbumpark
  • 233
  • 2
  • 14