3

I am creating a home automation app that has allows plugin views.

I have the following code as proof of concept:

I have created an android library project with the interface for the plugin:

package com.strutton.android.UIPlugInLib;

import android.app.Dialog;

public interface IRDroidInterface  {
    public Dialog buildConfigDialog(int ID);
    // Other method signatures here
}

I have been able to create a class as a sample plugin in a separate project (and exported it to apk) which I push to my app's file directory:

package com.strutton.android.testloadclass;

import com.strutton.android.UIPlugInLib.IRDroidInterface;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.widget.Button;

public class MyTestClass_IRDroidUIPlugIn extends Button implements IRDroidInterface{
    public final Activity mActivity;
    public MyTestClass_IRDroidUIPlugIn(Activity activity) {
        super((Context)activity);
        mActivity = activity;
        setText("I was loaded dynamically! (1)");
        setOnClickListener(new View.OnClickListener() {  
            public void onClick(View v) {  
                setText("I was Clicked dynamically! (" + getId() +")");
            }}  
                );
    }

    public Dialog buildConfigDialog(int ID){
        AlertDialog.Builder builder = new AlertDialog.Builder((Context)mActivity);
        builder.setMessage("Click the Button...(1)")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    mActivity.dialogDismiss();
               }           
           });
        return builder.create();
    }
}

I can load this class at run time and create an instance of it (in my onCreate()) using the following code:

package com.strutton.android.testplugin;

        try {
        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<View> classToLoad = 
                (Class<View>) classloader.loadClass("com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn");
        mybutton = (View) classToLoad.getDeclaredConstructor(Context.class).newInstance(this);
        mybutton.setId(2);
        main.addView((View)mybutton);
      } catch (Exception e) {
        e.printStackTrace();
    }

    setContentView(main);
}
protected Dialog onCreateDialog(int id) {
    switch (id) {
        case 1:
                    // this is the offending line 57
            return ((IRDroidInterface) mybutton).buildConfigDialog(id);
    }
    return null;
}

I want the plugin to be able to show a configuration dialog defined in the plugin. When I call buildConfigDialog(id) I get the following ClassCastException:

04-20 20:49:45.865: W/System.err(354): java.lang.ClassCastException: com.strutton.android.testloadclass.MyTestClass_IRDroidUIPlugIn
04-20 20:49:45.894: W/System.err(354): at com.strutton.android.testplugin.TestpluginActivity.onCreate(TestpluginActivity.java:57)

What am I missing here? I have been googling for a day and a half now and can't find a solution.

Thanks in advance.

Keale
  • 3,924
  • 3
  • 29
  • 46
cstrutton
  • 5,667
  • 3
  • 25
  • 32

2 Answers2

3

I suspect the problem is that the IRDroidInterface interface exists in both your application, and in the dex file you load. When you dynamically load the MyTestClass_IRDroidUIPlugIn class, since it is from the dex file, it implements the interface class from the dex file, not the one from your application.

If you can ensure that the interface class doesn't get included in the dex file for the plugin, I believe what you have should work fine.

Unfortunately, this might be tricky to do if you're building the plugin apk with eclipse or ant. One (rather ugly) solution would be to remove the interface class from the dex file after it is built. I.e., disassemble it with baksmali, delete the .smali file for the interface, and then reassemble with smali.

JesusFreke
  • 19,784
  • 5
  • 65
  • 68
  • I was hoping that using the Interface as a library in both projects would get me around that. Everything I have read about ClassCastException tells me your right though. – cstrutton Apr 22 '12 at 04:59
  • Can you check if the IRDroidInterface class exists in the dex file for your plugin? i.e. use baksmali to disassemble, and see if com/strutton/android/UIPlugInLib/IRDroidInterface.smali exists. – JesusFreke Apr 22 '12 at 05:06
  • Ah ha. I just got this to work (being able to cast to the interface directly) in a little test of mine - if I made sure that the interface didn't exist in the dex file that was being dynamically loaded. – JesusFreke Apr 22 '12 at 05:07
  • Ok... Not sure I know what you mean... Did you not include the `implements fooInterface` in the class def, or how did you accomplish that? – cstrutton Apr 22 '12 at 05:12
  • well, I cheated a bit. I was doing everything manually from the command line. So I had a fooInterface.java file and a foo.java file, and compiled both of them. And then deleted fooInterface.class before creating the dex file with dx – JesusFreke Apr 22 '12 at 05:13
  • I guess its time I figure out the manual build tools. It was going to happen sooner or later. I wonder if there is anyway to get Eclipse to build it?... (I doubt it) :( – cstrutton Apr 22 '12 at 11:40
  • There might be. If you can figure out how to add a reference to a jar containing the interface, that doesn't get compiled in. I don't use eclipse though, so I can't help there. – JesusFreke Apr 22 '12 at 17:35
  • The answer was in the way I set up the projects in eclipse. My original plan was 3 projects: The interface, the plugin referencing the interface as a lib and the main app referencing the interface as a lib. This didn't work. I put the interface in the main app and the plugin references the main app and now everything works as expected. Now, I can build and debug everything in eclipse. I just have to export the plugin as an APK and push it to the main app's files directory and everything works. Thanks to JesusFreke for pointing me in the right direction. – cstrutton Apr 24 '12 at 13:56
0

you can use your custom implementation of DexClassLoader to solve this problem. override findclass and return only interface that is in your app not library.

package re.execute;

import dalvik.system.DexClassLoader;

public class DexLoader extends DexClassLoader{
    public DexLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
    super(dexPath, optimizedDirectory, libraryPath, parent);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    if("re.execute.ILoader".equals(name)){
        return ILoader.class;
    }
    return super.findClass(name);
}
}