3

I am building a home automation app. I am trying to add a plugin system. I have as a test exported a test class (that subclasses Button) as an APK file and put it in my app's file directory. I was able to create a new instance of this class and put it in my View using DexClassLoader and .loadClass.

The next step is to scan all the APK's in this directory and get the names of the classes in them.

I found the DexFile class that does just that however it throws the following exceptions:

04-18 17:26:15.697: E/dalvikvm(726): Can't open dex cache '/data/dalvik-cache/data@data@com.strutton.android.testplugin@files@testloadclass.apk@classes.dex': No such file or directory

04-18 17:26:15.705: I/dalvikvm(726): Unable to open or create cache for /data/data/com.strutton.android.testplugin/files/testloadclass.apk (/data/dalvik-cache/data@data@com.strutton.android.testplugin@files@testloadclass.apk@classes.dex)

It would seem that it is trying to find the optimized DexFile in the system cache but I do not have permissions to the cache directory. From what I can see this is probably by design and I don't have a problem with that. Is there another way to parse a DEX file and get the class names from it?

I have looked at the source for some of the dex decompiler projects. Do I have to roll my own solution?

Here is my test app code (from my Activity OnCreate) just in case I missed something:

    try {
        ArrayList<String> UIPlugins = new ArrayList<String>();
        final File filesDir = this.getFilesDir();
        final File tmpDir = getDir("dex", 0);
        final DexClassLoader classloader = new DexClassLoader( filesDir.getAbsolutePath()+"/testloadclass.apk",
                tmpDir.getAbsolutePath(),
                null, this.getClass().getClassLoader());
        final Class<Button> classToLoad = 
                (Class<Button>) classloader.loadClass("com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn");
        Button mybutton = classToLoad.getDeclaredConstructor(Context.class).newInstance(this);
        mybutton.setId(2);
        mybutton.setOnClickListener(this);
        main.addView(mybutton);
        // Up to here everything works as expected
        // This line throws the exceptions
        DexFile mDexFile = new DexFile(filesDir.getAbsolutePath()+"/testloadclass.apk";);
        Enumeration<String> classNames = mDexFile.entries();
        while (classNames.hasMoreElements()){
            String className = classNames.nextElement();
            if (className.endsWith("IRDroidUIPlugIn")){
                UIPlugins.add(className);
            }
        }
        final Class<Button> classToLoad = 
                (Class<Button>) classloader.loadClass("com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn");
        Button mybutton = classToLoad.getDeclaredConstructor(Context.class).newInstance(this);
        mybutton.setId(2);
        mybutton.setOnClickListener(this);
        main.addView(mybutton);
        btn.setText(tmpDir.getAbsolutePath());
      } catch (Exception e) {
        e.printStackTrace();
    }
Padma Kumar
  • 19,893
  • 17
  • 73
  • 130
cstrutton
  • 5,667
  • 3
  • 25
  • 32

2 Answers2

4

Instead of:

DexFile mDexFile = new DexFile(filesDir.getAbsolutePath()+"/testloadclass.apk";);

try:

DexFile mDexFile = DexFile.loadDex(filesDir.getAbsolutePath()+"/testloadclass.apk", tmpDir.getAbsolutePath()+"/testloadclass.odex", 0);
JesusFreke
  • 19,784
  • 5
  • 65
  • 68
  • This is the log cat from your suggestion: `04-18 18:06:27.494: E/dalvikvm(833): DexOpt: failed reading opt header: Is a directory` `04-18 18:06:27.494: W/dalvikvm(833): Cached DEX '/data/data/com.strutton.android.testplugin/files/testloadclass.apk'(/data/data/com.strutton.android.testplugin/app_dex) is stale and not writable` `04-18 18:06:27.494: I/dalvikvm(833): Unable to open or create cache for /data/data/com.strutton.android.testplugin/files/testloadclass.apk (/data/data/com.strutton.android.testplugin/app_dex)` – cstrutton Apr 18 '12 at 18:14
  • Updated - It looks like the second path to loadDex should be a file, not a directory. – JesusFreke Apr 18 '12 at 18:41
0

Another approach you might consider, would be to install the plugins as separate applications. You could have the plugin application expose a service, using a particular intent that you define. You would also need a pre-defined aidl interface that the service would implement. In your main application, you could then resolve that service intent, to find any plugin services.

JesusFreke
  • 19,784
  • 5
  • 65
  • 68
  • I might use this approach for backend services. For example code that would talk to an X10 controller or other devices. However everything I have seen on the subject says this approach won't work very well for user interface elements. The concerns were related to the overhead required for inter process communications if I remember rightly (it been a while since I looked at that). – cstrutton Apr 18 '12 at 19:00
  • If your user interface needs aren't too great, you might be able to use the RemoteView functionality to provide the interface component of the plugin. However, the functionality of RemoteViews are fairly limited, so you would need to determine if they would be functional enough to meet your needs. – JesusFreke Apr 18 '12 at 19:19