Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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);
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top