镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

'NoSuchMethodError' calling static interface method on pre-API-24 #6784

Closed

Description

@vas1l3nk

Android application type

Classic Xamarin.Android (MonoAndroid12.0, etc.)

Affected platform version

VSMac 8.10.19(build 2)

Description

The problem occurs when I try to call an initialisation native method from my binding library (.aar). I know that the method exist in my Android Binding Library. Deleting folders bin and obj and rebuilding the project does not help. I also watched the file api.xml this method is there, and and I can refer to it. Removing or adding a method to Metadata.xml also doesn't help.
I also decompiled the .dll file and saw this method there.

method: Start(global::Android.Content.Context context, global::package.name.Configuration configuration)
The error I'm getting

Java.Lang.NoSuchMethodError
no static method "Lpackage/name/SDK;.start(Landroid/content/Context;Lpackage/name/Configuration;)V"

Steps to Reproduce

Create Android Binding library via .arr, which needs to be initialized in MyApplication.cs
Add reference from the Xamarin.Android project to Binding library.
Start the app

Did you find any workaround?

No response

Relevant log output

No response

Activity

  1. jpobst commented on Feb 22, 2022

    @jpobst
    Contributor

    This error means the Java compiled method cannot be found. There are a few possible cases to investigate:

    • The classes.jar from the .aar is not being included in the binding library. Make sure your build action is set to LibraryProjectZip. This will add the .aar as an embedded resource in your binding librarly .dll.
    • The Java linker is removing the method. You will need to investigate Proguard rules if your method is getting removed from the Java .jar.

    You probably don't want to mess with the Metadata for this case. Using the automatically generated one should create the correct mapping to the Java class.

  2. added
    Area: BindingsIssues in Java Library Binding projects.
    and removed
    needs-triageIssues that need to be assigned.
    on Feb 22, 2022
  3. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    I added proguard rules to the native library, but nothing has changed.
    I added proguard rules to the binding project and xamarin.android app but still nothing changed.

  4. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    after adding proguard rules, I got this error

    Java.Lang.IncompatibleClassChangeError
    no static method "Lpackage/name/SDK;.start(Landroid/content/Context;Lpackage/name/Configuration;)V"
    
  5. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    Hi @jpobst, I found such a thing.
    Screenshot 0004-02-24 at 8 52 21 PM
    Screenshot 0004-02-24 at 10 36 23 PM

    the fact is that when initializing my library, one of the parameters is the context.
    I tried adding <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    it, but nothing changes either. could this be the cause of the error?

    and one more thing I noticed.
    Screenshot 0004-02-24 at 9 02 51 PM

    the native method takes android.content.context as one of the arguments. on the xamarin app side, I pass this:

    Android.Content.Context context = Android.App.Application.Context;
    Method.Start(context, myConfiguration);
    

    And before calling this method in "Application output", I get Attempt to remove non-JNI local reference, dumping thread

  6. jpobst commented on Feb 24, 2022

    @jpobst
    Contributor

    Can you post:

    • The Java source code of the method you are trying to bind. (You can omit the method body)
    • The C# binding code that gets generated for the method.
  7. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    @jpobst Thanks for a quick reply.
    Everything I have on the Java side
    @kotlin.jvm.JvmStatic public final fun start(context: android.content.Context, configuration: package.name.Configuration): kotlin.Unit { /* compiled code */ }

    C# code

    Metadata.xml XPath method reference: path="/api/package[@name='package.name']/interface[@name='Interface']/method[@name='start' and count(parameter)=2 and parameter[1][@type='android.content.Context'] and parameter[2][@type='package.name.Configuration']]"
    		[Register ("start", "(Landroid/content/Context;Lpackage/name/Configuration;)V", "")]
    		public static unsafe void Start (global::Android.Content.Context context, global::package.name.Configuration configuration)
    		{
    			const string __id = "start.(Landroid/content/Context;Lpackage/name/Configuration;)V";
    			try {
    				JniArgumentValue* __args = stackalloc JniArgumentValue [2];
    				__args [0] = new JniArgumentValue ((context == null) ? IntPtr.Zero : ((global::Java.Lang.Object) context).Handle);
    				__args [1] = new JniArgumentValue ((configuration == null) ? IntPtr.Zero : ((global::Java.Lang.Object) configuration).Handle);
    				_members.StaticMethods.InvokeVoidMethod (__id, __args);
    			} finally {
    				global::System.GC.KeepAlive (context);
    				global::System.GC.KeepAlive (configuration);
    			}
    		}
    
    		static readonly JniPeerMembers _members = new XAPeerMembers ("package/name/Class", typeof (Class));
    
    
  8. jpobst commented on Feb 24, 2022

    @jpobst
    Contributor

    Hmm, since the original code is Kotlin, and it's monkeying with how it will actually look in Java, let's see how it looks in Java.

    Use a Java decompiler on your .jar file and post how the method actually got compiled into Java. (I use http://java-decompiler.github.io if you don't already have a Java decompiler.)

    Additionally, this looks like a static method on an interface, which traditionally isn't supported in C#.

    You can try enabling <_EnableInterfaceMembers> if you want to support the static method on the C# interface: https://docs.microsoft.com/en-us/xamarin/android/release-notes/10/10.1#c-80-default-interface-members-for-bindings-projects.

    Alternatively, it looks like this is handled the old way where it has been moved to a class instead.

    Please post the C# class signature (including the [Register] attribute) for the C# type containing the Start method.

  9. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    @jpobst Thanks for reply. This is from java decompiler

    @Metadata(mv = {1, 6, 0}, k = 1, xi = 48, d1 = {"\000\f\n\002\030\002\n\002\020\000\n\002\b\b\bf\030\000 \0032\0020\001:\007\002\003\004\005\006\007\b\006\t"}, d2 = {"Lpackage/name/Name;", "", "sdk_release"})
    public interface Name {
      @NotNull
      public static final Companion Companion = Companion.$$INSTANCE;
      
      @JvmStatic
      static void start(@NotNull Context context, @NotNull Configuration configuration) {
        Companion.start(context, configuration);
      }
      
    

    C# signarure with [Register]:

    // Metadata.xml XPath interface reference: path="/api/package[@name='package.name']/interface[@name='Name']"
    	[Register ("package/name/Name", "", "Package.Name.INameInvoker")]
    	public partial interface IName : IJavaObject, IJavaPeerable {
    		private static readonly JniPeerMembers _members = new XAPeerMembers ("package/name/Name", typeof (IName), isInterface: true);
    
    
    		// Metadata.xml XPath field reference: path="/api/package[@name='package.name']/interface[@name='Name']/field[@name='Companion']"
    		[Register ("Companion")]
    		public static global::Package.Name.NameCompanion Companion {
    			get {
    				const string __id = "Companion.Lpackage/name/Name$Companion;";
    
    				var __v = _members.StaticFields.GetObjectValue (__id);
    				return global::Java.Lang.Object.GetObject<global::Package.Name.NameCompanion> (__v.Handle, JniHandleOwnership.TransferLocalRef);
    			}
    		}
    
    		// Metadata.xml XPath method reference: path="/api/package[@name='package.name']/interface[@name='Name']/method[@name='start' and count(parameter)=2 and parameter[1][@type='android.content.Context'] and parameter[2][@type='package.name.Name.Configuration']]"
    		[Register ("start", "(Landroid/content/Context;Lpackage/name/Name$Configuration;)V", "")]
    		public static unsafe void Start (global::Android.Content.Context context, global::Package.Name.NameConfiguration configuration)
    		{
    			const string __id = "start.(Landroid/content/Context;Lpackage/name/Name$Configuration;)V";
    			try {
    				JniArgumentValue* __args = stackalloc JniArgumentValue [2];
    				__args [0] = new JniArgumentValue ((context == null) ? IntPtr.Zero : ((global::Java.Lang.Object) context).Handle);
    				__args [1] = new JniArgumentValue ((configuration == null) ? IntPtr.Zero : ((global::Java.Lang.Object) configuration).Handle);
    				_members.StaticMethods.InvokeVoidMethod (__id, __args);
    			} finally {
    				global::System.GC.KeepAlive (context);
    				global::System.GC.KeepAlive (configuration);
    			}
    		}
    
    	}
    
    

    I have already tried to add :

    <PropertyGroup>
      <LangVersion>preview</LangVersion>
      <_EnableInterfaceMembers>true</_EnableInterfaceMembers>
    </PropertyGroup>
    

    but nothing has changed

  10. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    at first there was a class, but after restarting the mac and rebuild, the interface appeared lol

  11. jpobst commented on Feb 24, 2022

    @jpobst
    Contributor

    Is this still the exact error message you are getting?

    Java.Lang.NoSuchMethodError
    no static method "Lpackage/name/SDK;.start(Landroid/content/Context;Lpackage/name/Configuration;)V"
    

    I would expect Lpackage/name/Name;...... There is no type called SDK that I see.

    The other thing to check would be to decompile your C# binding assembly and see if it contains the compiled .jar as an embedded resource. The compiled Java code has to make it on to the device as well as the compiled C# code for it to work.

  12. vas1l3nk commented on Feb 24, 2022

    @vas1l3nk
    Author

    @jpobst Thanks for a qiuck reply. Yes, it's still the same error, SDK and Name are not the real names of packages and interfaces. I'm sorry if this has caused you any inconvenience.

    This is from AndroidLibraryProjects.zip and the same I see in classes.jar:

    
    @Metadata(mv = {1, 6, 0}, k = 1, xi = 48, d1 = {"\000\f\n\002\030\002\n\002\020\000\n\002\b\t\bf\030\000 \0042\0020\001:\b\002\003\004\005\006\007\b\t\006\n"}, d2 = {"Lpackage/name/Name;", "", "sdk_release"})
    public interface Name {
      @NotNull
      public static final Companion Companion = Companion.$$INSTANCE;
      
      @JvmStatic
      static void start(@NotNull Context context, @NotNull Configuration configuration) {
        Companion.start(context, configuration);
      }
    
    

    or did I misunderstand you?

    And my build action is set to LibraryProjectZip in my .aar file

  13. vas1l3nk commented on Feb 25, 2022

    @vas1l3nk
    Author

    @jpobst Hello, when I use these group properties

    <PropertyGroup>
      <LangVersion>preview</LangVersion>
      <_EnableInterfaceMembers>true</_EnableInterfaceMembers>
    </PropertyGroup>
    

    I generate an interface like here:

    // Metadata.xml XPath interface reference: path="/api/package[@name='package.name']/interface[@name='Name']"
    	[Register ("package/name/Name", "", "Package.Name.INameInvoker")]
    	public partial interface IName : IJavaObject, IJavaPeerable {
    		private static readonly JniPeerMembers _members = new XAPeerMembers ("package/name/Name", typeof (IName), isInterface: true);
    
    
    		// Metadata.xml XPath field reference: path="/api/package[@name='package.name']/interface[@name='Name']/field[@name='Companion']"
    		[Register ("Companion")]
    		public static global::Package.Name.NameCompanion Companion {
    			get {
    				const string __id = "Companion.Lpackage/name/Name$Companion;";
    
    				var __v = _members.StaticFields.GetObjectValue (__id);
    				return global::Java.Lang.Object.GetObject<global::Package.Name.NameCompanion> (__v.Handle, JniHandleOwnership.TransferLocalRef);
    			}
    		}
    
    		// Metadata.xml XPath method reference: path="/api/package[@name='package.name']/interface[@name='Name']/method[@name='start' and count(parameter)=2 and parameter[1][@type='android.content.Context'] and parameter[2][@type='package.name.Name.Configuration']]"
    		[Register ("start", "(Landroid/content/Context;Lpackage/name/Name$Configuration;)V", "")]
    		public static unsafe void Start (global::Android.Content.Context context, global::Package.Name.NameConfiguration configuration)
    		{
    			const string __id = "start.(Landroid/content/Context;Lpackage/name/Name$Configuration;)V";
    			try {
    				JniArgumentValue* __args = stackalloc JniArgumentValue [2];
    				__args [0] = new JniArgumentValue ((context == null) ? IntPtr.Zero : ((global::Java.Lang.Object) context).Handle);
    				__args [1] = new JniArgumentValue ((configuration == null) ? IntPtr.Zero : ((global::Java.Lang.Object) configuration).Handle);
    				_members.StaticMethods.InvokeVoidMethod (__id, __args);
    			} finally {
    				global::System.GC.KeepAlive (context);
    				global::System.GC.KeepAlive (configuration);
    			}
    		}
    
    	}
    

    this is handled the old way where it has been moved to a class instead.
    well in two cases I still get an error:

    Java.Lang.NoSuchMethodError
    no static method "Lpackage/name/Name;.start(Landroid/content/Context;Lpackage/name/Configuration;)V"
    
  14. vas1l3nk commented on Feb 25, 2022

    @vas1l3nk
    Author

    @jpobst I've been looking for the root of the problem for a very long time, and apparently I found it. I looked through the classes.dex file and saw there that I don't have a Name type, there is only a Name$-CC type.

    Class #0            -
      Class descriptor  : 'Lpackage/name/Name$-CC;'
      Access flags      : 0x1011 (PUBLIC FINAL SYNTHETIC)
      Superclass        : 'Ljava/lang/Object;'
      Interfaces        -
      Static fields     -
      Instance fields   -
      Direct methods    -
        #0              : (in Lpackage/name/Name$-CC;)
          name          : 'start'
          type          : '(Landroid/content/Context;Lpackage/name/Name$Configuration;)V'
          access        : 0x0009 (PUBLIC STATIC)
          code          -
          registers     : 3
          ins           : 2
          outs          : 3
          insns size    : 6 16-bit code units
          catches       : (none)
          positions     : 
          locals        : 
      Virtual methods   -
      source_file_idx   : 8937 (Name.kt)
    

    please tell me what I can do in this case

  15. vas1l3nk commented on Feb 25, 2022

    @vas1l3nk
    Author

    I found workaround for this issue.
    I changed the name of the Register from [Register ("package/name/Name", "", "Package.Name.INameInvoker")] to [Register ("package/name/Name$-CC", "", "Package.Name.INameInvoker")].

    but maybe there is some other way?

  16. jpobst commented on Mar 1, 2022

    @jpobst
    Contributor

    I guess if that works then it's valid. 🤷‍♂️

    I don't know why the type named Name isn't in the classes.dex.

  17. vas1l3nk commented on Mar 4, 2022

    @vas1l3nk
    Author

    I understand there is no solution to this problem yet?

  18. jpobst commented on Mar 4, 2022

    @jpobst
    Contributor

    Unfortunately not. Without a way to reproduce the error there isn't much we can do to investigate or fix it.

  19. jedusei commented on Apr 7, 2022

    @jedusei

    Hi @jpobst,

    I also experienced this issue some time last year when I started developing a binding for the Twilio Conversations SDK for Android.
    Calling the static interface methods (eg. ConversationsClient.create) always threw a Java.Lang.NoSuchMethodError. I peeked into the SDK package source code & saw that those static interface methods were simply calling through to the implementing class methods, so I worked around the issue by copying the generated C# code for those methods, and changing the class reference to point to the implementing class instead (example).

    I recently decided to dig deeper into this issue to find out exactly what the problem was. Here are my findings:

    Support for static interface methods in Android was introduced in API 24. To facilitate backward-compatibility for Android projects with Minimum Version or TargetVersion set lower than 24, the Android build tools (d8 maybe?) extract all the static methods of the interface into a separate class (similar to how the Xamarin binding tools generate an additional class for interfaces that contain static methods), whose name is name of the interface + "$-CC", and modifies the calling code to call the static methods in that class instead.

    I verified this by creating a JAR containing one interface that has one static method that simply adds 2 integers and returns the result. I then created a binding project for that and added the binding to a blank Xamarin Android project. The app calls the method and displays the result on the screen. You can find those here. The calling code is in MainActivity.cs.

    You can observe the issue yourself by cloning the repo and performing the following steps:

    • Set Minimum Android version < API 24
    • Clean & Run the app
    • An error should show on the the screen
    • Set Minimum Android version >= API 24
    • Clean & Run the app
    • The adder result should show on the screen

    A possible solution might be to add code in the binding interface to check for the presence of a "InterfaceName$-CC" class and, if it exists, use its handle to invoke the static interface method. If it doesn't exist, then the interface handle can be used instead.

    Hope this helps!

  20. jpobst commented on Apr 7, 2022

    @jpobst
    Contributor

    Thanks for the investigation! Things like this are never documented anywhere and we generally have to decode and guess what's going on.

  21. changed the title [-]Java.Lang.NoSuchMethodError: 'No static method' when call method[/-] [+]'NoSuchMethodError' calling static interface method on pre-API-24[/+] on Apr 7, 2022
  22. sahil2441 commented on May 6, 2022

    @sahil2441

    I'm noticing the same issue with RxJava 3 upgrade on API < 24.

  23. added this to the Under Consideration milestone on Dec 1, 2022
  24. jpobst commented on Aug 3, 2023

    @jpobst
    Contributor

    This should be fixed for .NET 8+ due to dotnet/java-interop#1077.

  25. locked and limited conversation to collaborators on Jan 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Area: BindingsIssues in Java Library Binding projects.

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions