How to call a method (reflection) with a non-common parameter?

I have a little problem. I am developing an Android application. There you can dynamically load classes from other applications (packages). First of all, I don’t want to “hack” a third-party application, I want to try to create plugins for my own application. So what do I have?

2 test applications and 1 library included in both applications.

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 a class com.ftpsynctest.app2.classcall and call the method change with a parameter of type com.syncoorp.ftpsyncx. commons.SyncFile .

My application code 2:

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

First I set app2 to provide a class for app1. After that I started app1.

My conclusion:

01-10 22: 21: 48.804: INFO / System.out (4681): Method: change | 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 method found is com.syncoorp.ftpsyncx.commons.SyncFile and mine provided that one of them.

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 a SyncFile, and I put SyncFile in the invoke command. What is the problem? Could it be that compiling app2 creates a class from SyncFile that is different from compiled application1? if so, why? The SyncFile class is the same physical class in my "commons" library, shared by both projects.

Does anyone have a solution or answer?

+6
source share
2 answers

Edit: This answer is incorrect. See the counterexample below.

You formatted your argument to Method.invoke() incorrectly. See the documentation here . Instead of passing a single array of Object [] from all arguments, you simply pass multiple arguments to the call. What the designation Object... args means: the method accepts any number of objects.

In your example, change

 m.invoke(c.newInstance(), new Object[] { new com.syncoorp.ftpsyncx.commons.SyncFile("bla") }); 

to

 m.invoke(c.newInstance(), new com.syncoorp.ftpsyncx.commons.SyncFile("bla")); 

should fix the problem.


counterexample:

Refl.java:

 package com.drfloob.so; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; public class Refl { public static void main(String[] args) { try { Class c = Class.forName("com.drfloob.so.Refl2", true, ClassLoader.getSystemClassLoader()); 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(), "test 1"); m.invoke(c.newInstance(), new Object[] {"test 2"}); break; } } catch (Exception e) { e.printStackTrace(); } } } 

Refl2.java:

 package com.drfloob.so; public class Refl2 { public Refl2() { System.out.println(Refl2.class); } public void doStuff(String str) { System.out.println(str); } } 
0
source

In this case, there may be two classes with the same SyncFile names visible to different class loaders. Even if the classes are named the same, in the same package, even with the same byte code, they will be considered by virtual machines as different classes, since they come from different places (class loaders).

At run time, the identity of a class is determined by its package, its name, and the instance of the class loader that loaded it. It is expected that each class can be found / loaded with only one class loader. If this is not the case, the result will depend on which class loader acts when accessing the class.

new com.syncoorp.ftpsyncx.commons.SyncFile is likely to use the class loaded and associated with the local application class loader, while the called method expects the version associated with myClassLoader . Since both class loaders know the "same" class (identified by the package + class name), but each knows only one of them, from the point of view of the JVM, these are two different classes.

You could try to create your SyncFile instance using 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! 

Please note that you will encounter this problem wherever your application and the “plugin” exchange instances of any class that they both contain, that is, reflection everywhere. Think about whether it’s worth the hassle or if you want to resort to some better method, for example, IPC.

0
source

Source: https://habr.com/ru/post/1390319/


All Articles