xamarin / GooglePlayServicesComponents

Other
314 stars 146 forks source link

Xamarin.GooglePlayServices.TfLite.Support : Java.Lang.NoSuchMethodError: No static method InterpreterApi.Create #773

Closed shuaga closed 1 year ago

shuaga commented 1 year ago

Xamarin.Android Version (eg: 6.0):

13.2.0.0

Operating System & Version (eg: Mac OSX 10.11):

Mac OS 13.3.1

Describe your Issue

The app crashes after launch with the following error -

{Java.Lang.NoSuchMethodError: no static method "Lorg/tensorflow/lite/InterpreterApi;.create(Ljava/nio/ByteBuffer;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;"
  at Java.Interop.JniEnvironment+StaticMethods.GetStaticMethodID (Java.Interop.JniObjectReference type, System.String name, System.String signature) [0x00055] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/JniEnvironment.g.cs:12847 
  at Java.Interop.JniType.GetStaticMethod (System.String name, System.String signature) [0x00006] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniType.cs:315 
  at Java.Interop.JniPeerMembers+JniStaticMethods.GetMethodInfo (System.String method, System.String signature) [0x00000] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs:66 
  at Java.Interop.JniPeerMembers+JniStaticMethods.GetMethodInfo (System.String encodedMember) [0x0003f] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs:34 
  at Java.Interop.JniPeerMembers+JniStaticMethods.InvokeObjectMethod (System.String encodedMember, Java.Interop.JniArgumentValue* parameters) [0x00000] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs:150 
  at Xamarin.TensorFlow.Lite.InterpreterApi.Create (Java.Nio.ByteBuffer byteBuffer, Xamarin.TensorFlow.Lite.InterpreterApiOptions options) [0x00053] in <fa86823f65a141d2b9e0cd7a1578182a>:0 
  ........
  --- End of managed Java.Lang.NoSuchMethodError stack trace ---
java.lang.NoSuchMethodError: no static method "Lorg/tensorflow/lite/InterpreterApi;.create(Ljava/nio/ByteBuffer;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;"
}

However, in the AssemblyBrowser, I see the static method Create is present.

Relevant information

The model works with the API InterpreterFactory. But it is deprecated and its replacement is throwing the above error. Sample code that works using the deprecated API -

      var initializeTask = await TfLite.Initialize(Platform.CurrentActivity);
      var mappedBuffer = TFLiteHelper.LoadModel("facenet");
  #pragma warning disable CS0618 // Type or member is obsolete
      var factory = new InterpreterFactory();*/
  #pragma warning restore CS0618 // Type or member is obsolete
      var options = new InterpreterApiOptions().SetRuntime(InterpreterApiOptions.TfLiteRuntime.FromSystemOnly);
      this.faceNetModel = factory.Create(mappedBuffer, options);

Packages used:

   <PackageReference Include="Xamarin.GooglePlayServices.TfLite.Support" Version="116.0.1.1" />
   <PackageReference Include="Xamarin.GooglePlayServices.TfLite.Java" Version="116.0.1.1" />

Minimal Repro Code Sample

    var initializeTask = await TfLite.Initialize(Platform.CurrentActivity);
    var mappedBuffer = TFLiteHelper.LoadModel("facenet");
    this.faceNetModel = InterpreterApi.Create(mappedBuffer, new InterpreterApiOptions());
moljac commented 1 year ago

@shuaga Thanks for the feedback.

However, in the AssemblyBrowser, I see the static method Create is present.

Try adding prouguard rules.

moljac commented 1 year ago

I think you need to add proguard rules. Seems like methods were removed by shrinker/optimizer (r8/proguard).

in api.xml there are 2 create methods

    <interface abstract="true" deprecated="not deprecated" final="false" name="InterpreterApi" static="false" visibility="public" jni-signature="Lorg/tensorflow/lite/InterpreterApi;">
      <implements name="java.lang.AutoCloseable" name-generic-aware="java.lang.AutoCloseable" jni-type="Ljava/lang/AutoCloseable;" />
      <method abstract="true" deprecated="not deprecated" final="false" name="allocateTensors" jni-signature="()V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public" />
      <method abstract="true" deprecated="not deprecated" final="false" name="close" jni-signature="()V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public" />
      <method abstract="false" deprecated="not deprecated" final="false" name="create" jni-signature="(Ljava/io/File;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;" bridge="false" native="false" return="org.tensorflow.lite.InterpreterApi" jni-return="Lorg/tensorflow/lite/InterpreterApi;" static="true" synchronized="false" synthetic="false" visibility="public">
        <parameter name="modelFile" type="java.io.File" jni-type="Ljava/io/File;" />
        <parameter name="options" type="org.tensorflow.lite.InterpreterApi.Options" jni-type="Lorg/tensorflow/lite/InterpreterApi$Options;" />
      </method>
      <method abstract="false" deprecated="not deprecated" final="false" name="create" jni-signature="(Ljava/nio/ByteBuffer;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;" bridge="false" native="false" return="org.tensorflow.lite.InterpreterApi" jni-return="Lorg/tensorflow/lite/InterpreterApi;" static="true" synchronized="false" synthetic="false" visibility="public">
        <parameter name="byteBuffer" type="java.nio.ByteBuffer" jni-type="Ljava/nio/ByteBuffer;" />
        <parameter name="options" type="org.tensorflow.lite.InterpreterApi.Options" jni-type="Lorg/tensorflow/lite/InterpreterApi$Options;" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="getInputIndex" jni-signature="(Ljava/lang/String;)I" bridge="false" native="false" return="int" jni-return="I" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="opName" type="java.lang.String" jni-type="Ljava/lang/String;" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="getInputTensor" jni-signature="(I)Lorg/tensorflow/lite/Tensor;" bridge="false" native="false" return="org.tensorflow.lite.Tensor" jni-return="Lorg/tensorflow/lite/Tensor;" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="inputIndex" type="int" jni-type="I" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="getInputTensorCount" jni-signature="()I" bridge="false" native="false" return="int" jni-return="I" static="false" synchronized="false" synthetic="false" visibility="public" />
      <method abstract="true" deprecated="not deprecated" final="false" name="getLastNativeInferenceDurationNanoseconds" jni-signature="()Ljava/lang/Long;" bridge="false" native="false" return="java.lang.Long" jni-return="Ljava/lang/Long;" static="false" synchronized="false" synthetic="false" visibility="public" />
      <method abstract="true" deprecated="not deprecated" final="false" name="getOutputIndex" jni-signature="(Ljava/lang/String;)I" bridge="false" native="false" return="int" jni-return="I" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="opName" type="java.lang.String" jni-type="Ljava/lang/String;" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="getOutputTensor" jni-signature="(I)Lorg/tensorflow/lite/Tensor;" bridge="false" native="false" return="org.tensorflow.lite.Tensor" jni-return="Lorg/tensorflow/lite/Tensor;" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="outputIndex" type="int" jni-type="I" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="getOutputTensorCount" jni-signature="()I" bridge="false" native="false" return="int" jni-return="I" static="false" synchronized="false" synthetic="false" visibility="public" />
      <method abstract="true" deprecated="not deprecated" final="false" name="resizeInput" jni-signature="(I[I)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="idx" type="int" jni-type="I" />
        <parameter name="dims" type="int[]" jni-type="[I" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="resizeInput" jni-signature="(I[IZ)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="idx" type="int" jni-type="I" />
        <parameter name="dims" type="int[]" jni-type="[I" />
        <parameter name="strict" type="boolean" jni-type="Z" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="run" jni-signature="(Ljava/lang/Object;Ljava/lang/Object;)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="input" type="java.lang.Object" jni-type="Ljava/lang/Object;" />
        <parameter name="output" type="java.lang.Object" jni-type="Ljava/lang/Object;" />
      </method>
      <method abstract="true" deprecated="not deprecated" final="false" name="runForMultipleInputsOutputs" jni-signature="([Ljava/lang/Object;Ljava/util/Map;)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
        <parameter name="inputs" type="java.lang.Object[]" jni-type="[Ljava/lang/Object;" />
        <parameter name="outputs" type="java.util.Map&lt;java.lang.Integer, java.lang.Object&gt;" jni-type="Ljava/util/Map&lt;Ljava/lang/Integer;Ljava/lang/Object;&gt;;" />
      </method>
    </interface>

and in MCWs

./generated/org.tensorflow.tensorflow-lite-api/obj/Release/monoandroid12.0/generated/src/Xamarin.TensorFlow.Lite.IInterpreterApi.cs

both create methods were bound:

        // Metadata.xml XPath method reference: path="/api/package[@name='org.tensorflow.lite']/interface[@name='InterpreterApi']/method[@name='create' and count(parameter)=2 and parameter[1][@type='java.io.File'] and parameter[2][@type='org.tensorflow.lite.InterpreterApi.Options']]"
        [Register ("create", "(Ljava/io/File;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;", "")]
        public static unsafe global::Xamarin.TensorFlow.Lite.IInterpreterApi Create (global::Java.IO.File modelFile, global::Xamarin.TensorFlow.Lite.InterpreterApiOptions options)
        {
            const string __id = "create.(Ljava/io/File;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;";
            try {
                JniArgumentValue* __args = stackalloc JniArgumentValue [2];
                __args [0] = new JniArgumentValue ((modelFile == null) ? IntPtr.Zero : ((global::Java.Lang.Object) modelFile).Handle);
                __args [1] = new JniArgumentValue ((options == null) ? IntPtr.Zero : ((global::Java.Lang.Object) options).Handle);
                var __rm = _members.StaticMethods.InvokeObjectMethod (__id, __args);
                return global::Java.Lang.Object.GetObject<global::Xamarin.TensorFlow.Lite.IInterpreterApi> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
            } finally {
                global::System.GC.KeepAlive (modelFile);
                global::System.GC.KeepAlive (options);
            }
        }

        // Metadata.xml XPath method reference: path="/api/package[@name='org.tensorflow.lite']/interface[@name='InterpreterApi']/method[@name='create' and count(parameter)=2 and parameter[1][@type='java.nio.ByteBuffer'] and parameter[2][@type='org.tensorflow.lite.InterpreterApi.Options']]"
        [Register ("create", "(Ljava/nio/ByteBuffer;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;", "")]
        public static unsafe global::Xamarin.TensorFlow.Lite.IInterpreterApi Create (global::Java.Nio.ByteBuffer byteBuffer, global::Xamarin.TensorFlow.Lite.InterpreterApiOptions options)
        {
            const string __id = "create.(Ljava/nio/ByteBuffer;Lorg/tensorflow/lite/InterpreterApi$Options;)Lorg/tensorflow/lite/InterpreterApi;";
            try {
                JniArgumentValue* __args = stackalloc JniArgumentValue [2];
                __args [0] = new JniArgumentValue ((byteBuffer == null) ? IntPtr.Zero : ((global::Java.Lang.Object) byteBuffer).Handle);
                __args [1] = new JniArgumentValue ((options == null) ? IntPtr.Zero : ((global::Java.Lang.Object) options).Handle);
                var __rm = _members.StaticMethods.InvokeObjectMethod (__id, __args);
                return global::Java.Lang.Object.GetObject<global::Xamarin.TensorFlow.Lite.IInterpreterApi> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
            } finally {
                global::System.GC.KeepAlive (byteBuffer);
                global::System.GC.KeepAlive (options);
            }
        }
shuaga commented 1 year ago

I have tried this in our DEBUG version of the app and it doesn't have 'Code Shrinker' enabled. Verified the Code-Shrinker configuration under Android -> Build -> Enable Multi-Dex -> Code Shrinker; neither Proguard nor r8 is selected.

In any case, we have moved to dotnet 7 and the above error doesn't repro in dotnet 7. The error is changed to
Abort message: 'JNI DETECTED ERROR IN APPLICATION: can't call static org.tensorflow.lite.InterpreterApi org.tensorflow.lite.InterpreterApi$-CC.create(java.nio.ByteBuffer, org.tensorflow.lite.InterpreterApi$Options) with class java.lang.Class<org.tensorflow.lite.InterpreterApi> in call to CallStaticObjectMethodA'

This new error indicates that the Create method was found. So, this current issue can be closed. I might end up creating another issue for the new error.

moljac commented 1 year ago

@shuaga

OK. I will close it and you can open new issue. And please if you can add some repro sample. I know sometimes it is not that easy, but it would help us a lot.