Java bindings metadata
A .NET for Android Java Binding Library tries to automate much of the work necessary for binding an existing Android library with the help of a tool sometimes known as the Bindings Generator. When binding a Java library, .NET for Android will inspect the Java classes and generate a list of all the packages, types, and members which to be bound. This list of APIs is stored in an XML file that can be found at {project directory}\obj{Configuration}\api.xml.
The Bindings Generator will use the api.xml file as a guideline for generating the necessary C# wrapper classes. The following snippet is an example of the contents of api.xml:
<api>
<package name="android">
<class abstract="false" deprecated="not deprecated" extends="java.lang.Object"
extends-generic-aware="java.lang.Object"
final="true"
name="Manifest"
static="false"
visibility="public">
<constructor deprecated="not deprecated" final="false"
name="Manifest" static="false" type="android.Manifest"
visibility="public">
</constructor>
</class>
...
</api>
In this example, api.xml declares a class in the android
package
named Manifest
that extends the java.lang.Object
.
In many cases, human assistance is required to make the Java API feel more ".NET like" or to correct issues that prevent the binding assembly from compiling. For example, it may be necessary to change Java package names to .NET namespaces, rename a class, or change the return type of a method.
These changes should not be achieved by modifying api.xml directly. Instead, changes are recorded in special XML files that are provided by the Java Binding Library template. When compiling the .NET for Android binding assembly, the Bindings Generator will be influenced by these mapping files when creating the binding assembly
The Metadata.xml file is the most important of these files as it allows general-purpose changes to the binding such as:
Renaming namespaces, classes, methods, or fields so they follow .NET conventions.
Removing namespaces, classes, methods, or fields that aren't needed.
Moving classes to different namespaces.
Adding additional support classes to make the design of the binding follow .NET framework patterns.
Metadata.xml transform file
As we've already learned, the file Metadata.xml is used by the Bindings Generator to influence the creation of the binding assembly. The metadata format uses XPath syntax.
This implementation is almost a complete implementation of XPath
1.0 and thus supports items in the 1.0 standard. This file is a
powerful XPath based mechanism to change, add, hide, or move any
element or attribute in the API file. All of the rule elements in the
metadata spec include a path
attribute to identify the node(s) to which
the rule is to be applied. The following are the available element types:
- add-node – Appends a child node to the node specified by the path attribute.
- attr – Sets the value of an attribute of the element specified by the path attribute.
- remove-node – Removes nodes matching a specified XPath.
The following is an example of a Metadata.xml file:
<metadata>
<!-- Normalize the namespace for .NET -->
<attr path="/api/package[@name='com.evernote.android.job']"
name="managedName">Evernote.AndroidJob</attr>
<!-- Don't need these packages for the .NET for Android binding/public API -->
<remove-node path="/api/package[@name='com.evernote.android.job.v14']" />
<remove-node path="/api/package[@name='com.evernote.android.job.v21']" />
<!-- Change a parameter name from the generic p0 to a more meaningful one. -->
<attr path="/api/package[@name='com.evernote.android.job']/class[@name='JobManager']/method[@name='forceApi']/parameter[@name='p0']"
name="name">api</attr>
</metadata>
The following lists some of the more commonly used XPath elements for the Java API's:
interface
– Used to locate a Java interface. e.g./interface[@name='AuthListener']
.class
– Used to locate a class . e.g./class[@name='MapView']
.method
– Used to locate a method on a Java class or interface. e.g./class[@name='MapView']/method[@name='setTitleSource']
.parameter
– Identify a parameter for a method. e.g./parameter[@name='p0']
Adding types
The add-node
element will tell the .NET for Android binding project to
add a new class to api.xml. For example, the following
snippet will direct the Binding Generator to create a class with a
constructor and a single field:
<add-node path="/api/package[@name='org.alljoyn.bus']">
<class abstract="false" deprecated="not deprecated" final="false" name="AuthListener.AuthRequest" static="true" visibility="public" extends="java.lang.Object">
<constructor deprecated="not deprecated" final="false" name="AuthListener.AuthRequest" static="false" type="org.alljoyn.bus.AuthListener.AuthRequest" visibility="public" />
<field name="p0" type="org.alljoyn.bus.AuthListener.Credentials" />
</class>
</add-node>
Removing types
It is possible to instruct the .NET for Android Bindings Generator to
ignore a Java type and not bind it. This is done by adding a
remove-node
XML element to the Metadata.xml file:
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
Renaming members
Renaming members cannot be done by directly editing the api.xml
file because .NET for Android requires the original Java
Native Interface (JNI) names to talk to Java. Therefore, the //class/@name
attribute
cannot be altered; if it is, the binding will not work.
Consider the case where we want to rename a type, android.Manifest
.
To accomplish this, we might try to directly edit api.xml and
rename the class like so:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="name">NewName</attr>
This will result in the Bindings Generator creating the following C# code for the wrapper class:
[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }
Notice that the wrapper class has been renamed to NewName
, while the
original Java type is still Manifest
. It is no longer possible for
the .NET for Android binding class to access any methods on
android.Manifest
; the wrapper class is bound to a non-existent Java
type.
To properly change the "managed" name of a wrapped type (or method), it
is necessary to set the managedName
attribute as shown in this
example:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
Using managedName
is required when trying to rename any member, such
as classes, interfaces, methods, and parameters.
Renaming EventArg
wrapper classes
When the .NET for Android binding generator identifies an onXXX
setter
method for a listener type, a C# event and EventArgs
subclass will
be generated to support a .NET flavoured API for the Java-based
listener pattern. As an example, consider the following Java class and
method:
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
.NET for Android will drop the prefix on
from the setter method and
instead use 2DSignNextManuever
as the basis for the name of the
EventArgs
subclass. The subclass will be named something similar to:
NavigationManager.2DSignNextManueverEventArgs
This is not a legal C# class name. To correct this problem, the binding
author must use the argsType
attribute and provide a valid C# name
for the EventArgs
subclass:
<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
interface[@name='NavigationManager.Listener']/
method[@name='on2DSignNextManeuver']"
name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>
Supported attributes
The following sections describe some of the attributes for transforming Java APIs.
argsType
This attribute is placed on setter methods to name the EventArg
subclass that will be generated to support Java listeners. This is
described in more detail the section
Renaming EventArg Wrapper Classes
in this guide.
eventName
Specifies a name for an event. If the name is empty, it prevents event generation. This is described in more detail in the section Renaming EventArg Wrapper Classes.
managedName
This is used to change the name of a package, class, method, or
parameter. For example to change the name of the Java class MyClass
to NewClassName
:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
The next example illustrates an XPath expression for renaming the
method java.lang.object.toString
to
Java.Lang.Object.NewManagedName
:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
managedType
managedType
is used to change the return type of a method. In some
situations the Bindings Generator will incorrectly infer the return
type of a Java method, which will result in a compile time error. One
possible solution in this situation is to change the return type of the
method.
For example, the Bindings Generator believes that the Java method
de.neom.neoreadersdk.resolution.compareTo()
should return an int
and take Object
as parameters,
which results in the error message Error CS0535:
'DE.Neom.Neoreadersdk.Resolution' does not implement interface member
'Java.Lang.IComparable.CompareTo(Java.Lang.Object)'.
The following
snippet demonstrates how to change the first parameter's type of the generated C#
method from a DE.Neom.Neoreadersdk.Resolution
to a Java.Lang.Object
:
<attr path="/api/package[@name='de.neom.neoreadersdk']/
class[@name='Resolution']/
method[@name='compareTo' and count(parameter)=1 and
parameter[1][@type='de.neom.neoreadersdk.Resolution']]/
parameter[1]" name="managedType">Java.Lang.Object</attr>
managedReturn
Changes the return type of a method. This does not change the return
attribute (as changes to return attributes can result in incompatible
changes to the JNI signature). In the following example, the return
type of the append
method is changed from SpannableStringBuilder
to
IAppendable
:
<attr path="/api/package[@name='android.text']/
class[@name='SpannableStringBuilder']/
method[@name='append']"
name="managedReturn">Java.Lang.IAppendable</attr>
obfuscated
Tools that obfuscate Java libraries may interfere with the .NET for Android Binding Generator and its ability to generate C# wrapper classes. Characteristics of obfuscated classes include:
- The class name includes a $, i.e. a$.class
- The class name is entirely compromised of lower case characters, i.e. a.class
This snippet is an example of how to generate an "un-obfuscated" C# type:
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
This attribute can be used to change the name of a managed property.
A specialized case of using propertyName
involves the situation where
a Java class has only a setter method for a field. In this situation
the Binding Generator would want to create a write-only property,
something that is discouraged in .NET. The following snippet shows how
to "remove" the .NET properties by setting the propertyName
to an
empty string:
<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='setResourceDescriptor'
and count(parameter)=1
and parameter[1][@type='java.lang.String']]"
name="propertyName"></attr>
<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='getResourceDescriptor'
and count(parameter)=0]"
name="propertyName"></attr>
Note that the setter and getter methods will still be created by the Bindings Generator, they just will not be converted to a .NET property.
sender
Specifies which parameter of a method should be the sender
parameter
when the method is mapped to an event. The value can be true
or
false
. For example:
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
visibility
This attribute is used to change the visibility of a class, method, or
property. For example, it may be necessary to promote a protected
Java method so that it's corresponding C# wrapper is public
:
<!-- Change the visibility of a class -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']" name="visibility">public</attr>
<!-- Change the visibility of a method -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']/method[@name='MethodName']" name="visibility">public</attr>