Java Bindings Metadata
Important
We're currently investigating custom binding usage on the Xamarin platform. Please take this survey to inform future development efforts.
C# code in Xamarin.Android calls Java libraries through bindings, which are a mechanism that abstracts the low-level details that are specified in Java Native Interface (JNI). Xamarin.Android provides a tool that generates these bindings. This tooling lets the developer control how a binding is created by using metadata, which allows procedures such as modifying namespaces and renaming members. This document discusses how metadata works, summarizes the attributes that metadata supports, and explains how to resolve binding problems by modifying this metadata.
Overview
A Xamarin.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, Xamarin.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\Release\api.xml for a RELEASE build and at {project directory}\obj\Debug\api.xml for a DEBUG build.
The Bindings Generator will use the api.xml file as a guideline for generating the necessary C# wrapper classes. The contents of this XML file are a variation of Google's Android Open Source Project format. 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 are not 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 Xamarin.Android binding assembly, the Bindings Generator will be influenced by these mapping files when creating the binding assembly
These XML mapping files may be found in the Transforms folder of the project:
MetaData.xml – Allows changes to be made to the final API, such as changing the namespace of the generated binding.
EnumFields.xml – Contains the mapping between Java
int
constants and C#enums
.EnumMethods.xml – Allows changing method parameters and return types from Java
int
constants to C#enums
.
The MetaData.xml file is the most import 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.
Lets move on to discuss Metadata.xml in more detail.
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 and is nearly identical to the GAPI Metadata described in GAPI Metadata guide. 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 to which the rule is to be applied. The rules are applied in the following order:
- 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 Xamarin 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 Xamarin.Android binding project to
add a new wrapper 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 Xamarin.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 Xamarin.Android requires the original Java
Native Interface (JNI) names. 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 Xamarin.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>
Renaming EventArg
Wrapper Classes
When the Xamarin.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);
Xamarin.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 below in the section
Renaming EventArg Wrapper Classes
later on in this guide.
eventName
Specifies a name for an event. If empty, it inhibits event generation. This is described in more detail in the section title 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
(recall that C# does not support covariant return types):
<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 Xamarin.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 getter 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.
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>
EnumFields.xml and EnumMethods.xml
There are cases where Android libraries use integer constants to represent states that are passed to properties or methods of the libraries. In many cases, it is useful to bind these integer constants to enums in C#. To facilitate this mapping, use the EnumFields.xml and EnumMethods.xml files in your binding project.
Defining an Enum using EnumFields.xml
The EnumFields.xml file contains the mapping between Java int
constants and C# enums
. Let's take the following example of a C# enum
being created for a set of int
constants:
<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit">
<field jni-name="UNIT_SECOND" clr-name="Second" value="0" />
<field jni-name="UNIT_METER" clr-name="Meter" value="1" />
<field jni-name="UNIT_MILIWATT_HOURS" clr-name="MilliwattHour" value="2" />
</mapping>
Here we have taken the Java class SKRealReachSettings
and defined a
C# enum called SKMeasurementUnit
in the namespace
Skobbler.Ngx.Map.RealReach
. The field
entries defines the name of
the Java constant (example UNIT_SECOND
), the name of the enum entry
(example Second
), and the integer value represented by both
entities (example 0
).
Defining Getter/Setter Methods using EnumMethods.xml
The EnumMethods.xml file allows changing method parameters and
return types from Java int
constants to C# enums
. In other words,
it maps the reading and writing of C# enums (defined in the
EnumFields.xml file) to Java int
constant get
and set
methods.
Given the SKRealReachSettings
enum defined above, the following
EnumMethods.xml file would define the getter/setter for this enum:
<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings">
<method jni-name="getMeasurementUnit" parameter="return" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
<method jni-name="setMeasurementUnit" parameter="measurementUnit" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
</mapping>
The first method
line maps the return value of the Java
getMeasurementUnit
method to the SKMeasurementUnit
enum. The
second method
line maps the first parameter of the
setMeasurementUnit
to the same enum.
With all of these changes in place, you can use the follow code in
Xamarin.Android to set the MeasurementUnit
:
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
Summary
This article discussed how Xamarin.Android uses metadata to transform an API definition from the Google AOSP format. After covering the changes that are possible using Metadata.xml, it examined the limitations encountered when renaming members and it presented the list of supported XML attributes, describing when each attribute should be used.