Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to invoke a method (reflection) with a not generic parameter?

I have a little problem. I am developing an Android applikation. There you can dynamicly load classes from other applications (packages). First of all, i do not want to "hack" an third-party app, i want to try to build up plugins for my own app. So what do i have?

2 Test applications and 1 library which is in both apps included.

So the code for app1:

package com.ftpsynctest.app1;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import com.syncoorp.ftpsyncx.commons.SyncFile;
import dalvik.system.PathClassLoader;
public class App1Activity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        SyncFile f = new SyncFile("bla");
        String classname = "com.ftpsynctest.app2.classcall";
        String classpath = getApk("com.ftpsynctest.app1") + ":" + getApk("com.ftpsynctest.app2");
        PathClassLoader myClassLoader = new dalvik.system.PathClassLoader(classpath, ClassLoader.getSystemClassLoader());
        try {
            Class c = Class.forName(classname, true, myClassLoader);
            for (Method m : c.getDeclaredMethods()) {
                System.out.println("Method: " + m.getName());
                for (Type t : m.getGenericParameterTypes()) {
                    System.out.println(" - type: " + t.toString());
                }
                m.invoke(c.newInstance(), new Object[] {
                    new com.syncoorp.ftpsyncx.commons.SyncFile("bla")
                });
                break;
            }
        }
        catch (ClassNotFoundException e) {e.printStackTrace();}
        catch (IllegalArgumentException e) {e.printStackTrace();}
        catch (IllegalAccessException e) {e.printStackTrace();}
        catch (InvocationTargetException e) {e.printStackTrace();}
        catch (InstantiationException e) {e.printStackTrace();}
    }

    private String getApk(String packageName) {
        try { return this.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;}
        catch (NameNotFoundException e) {e.printStackTrace();}
        return "";
    }
}

So i want to create the class com.ftpsynctest.app2.classcall and call the method modify with a parameter of type com.syncoorp.ftpsyncx.commons.SyncFile.

My app2 code:

package com.ftpsynctest.app2;
import com.syncoorp.ftpsyncx.commons.SyncFile;
public class classcall {
    public SyncFile modify(SyncFile file) {
        file.change_date = 123;
        return file;
    }
}

I first installed app2 to provide the class to app1. After that succeed i started app1.

My Output:

01-10 22:21:48.804: INFO/System.out(4681): Method: modify
01-10 22:21:48.809: INFO/System.out(4681): - type: class com.syncoorp.ftpsyncx.commons.SyncFile

So for now it looks good. the parameter type of the found method is com.syncoorp.ftpsyncx.commons.SyncFile and my provided one is the same.

But i get the following error:

 java.lang.IllegalArgumentException: argument type mismatch  
      at java.lang.reflect.Method.invokeNative(Native Method)  
      at java.lang.reflect.Method.invoke(Method.java:507)  
      at com.ftpsynctest.app1.App1Activity.onCreate(App1Activity.java:44)  
      at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1615)  
      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1667)  
      at android.app.ActivityThread.access$1500(ActivityThread.java:117)  
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)  
      at android.os.Handler.dispatchMessage(Handler.java:99)  
      at android.os.Looper.loop(Looper.java:130)  
      at android.app.ActivityThread.main(ActivityThread.java:3691)  
      at java.lang.reflect.Method.invokeNative(Native Method)  
      at java.lang.reflect.Method.invoke(Method.java:507)  
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)  
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)  
      at dalvik.system.NativeStart.main(Native Method)

But why? my output tells me that it is SyncFile and i put SyncFile to the invoke command. Whats the problem there? Can it be that compiling app2 creates a class from SyncFile which is different from the compiled app1 ? if yes, why ? the SyncFile class is the same physical class within my "commons" library which both projects share.

Anybody has a solution or answer?

like image 630
prdatur Avatar asked Jan 10 '12 21:01

prdatur


People also ask

How do I use reflection to call a generic method?

The first step to dynamically invoking a generic method with reflection is to use reflection to get access to the MethodInfo of the generic method. To do that simply do this: var methodInfo = typeof(ClassWithGenericMethod). GetMethod("MethodName");

How do you call a method of a generic type?

You need to use reflection to get the method to start with, then "construct" it by supplying type arguments with MakeGenericMethod: MethodInfo method = typeof(Sample). GetMethod(nameof(Sample. GenericMethod)); MethodInfo generic = method.

What does method invoke return?

invoke() method returns the object which is returned after that method execution!


1 Answers

There may be two identically named classes SyncFile visible to different classloaders in this case. Even if the classes are named exactly the same, in the same package, even with the same byte code they will be considered different classes by the VM because they come from different locations (classloaders).

At runtime, the identity of a class is defined by its package, its name and the classloader instance that loaded it. It is expected that every class can only be found/loaded by exactly one classloader. If that is not the case, the result will vary based upon which classloader is in effect when the class is accessed.

new com.syncoorp.ftpsyncx.commons.SyncFile will probably use the class loaded by and associated with the app's local classloader whereas the called method expects the version associated with myClassLoader. Since both classloaders know the 'same' class (identified by package+class name), but each one only knows one of them, from the JVM's perspective, they are two different classes.

You could try and create your SyncFile instance via reflection from the SyncFile class loaded by myClassloader, i.e.

Class sfClass = Class.forName("com.syncoorp.ftpsyncx.commons.SyncFile", true, myClassLoader);
Object param = sfClass.newInstance("bla"); // param must be Object because the 'local' SyncFile is not the same as the SyncFile represented by sfClass! 

Note that you will face this problem everywhere where your app and a 'plugin' exchange instances of any class they both contain, i.e. reflection everywhere. Consider if it's worth the hassle or if you want to resort to some better way, e.g. IPC.

like image 67
JimmyB Avatar answered Oct 24 '22 21:10

JimmyB