Metadados de associações de Java
Importante
No momento, estamos investigando o uso de associação personalizada na plataforma Xamarin. Por favor, responda a esta pesquisa para informar os esforços de desenvolvimento futuros.
O código C# no Xamarin.Android chama bibliotecas Java por meio de associações, que são um mecanismo que abstrai os detalhes de baixo nível especificados na JNI (Interface Nativa Java). O Xamarin.Android fornece uma ferramenta que gera essas associações. Essas ferramentas permitem que o desenvolvedor controle como uma associação é criada usando metadados, o que permite procedimentos como modificar namespaces e renomear membros. Este documento discute como os metadados funcionam, resume os atributos aos quais os metadados dão suporte e explica como resolver problemas de associação modificando esses metadados.
Visão geral
Uma Biblioteca de Associação Java do Xamarin.Android tenta automatizar grande parte do trabalho necessário para associar uma biblioteca Android existente com a ajuda de uma ferramenta às vezes conhecida como Gerador de Associações. Ao associar uma biblioteca Java, o Xamarin.Android inspecionará as classes Java e gerará uma lista de todos os pacotes, tipos e membros a serem associados. Essa lista de APIs é armazenada em um arquivo XML que pode ser encontrado em {diretório do projeto}\obj\Release\api.xml para uma compilação RELEASE e em {diretório do projeto}\obj\Debug\api.xml para uma compilação DEBUG .
O Gerador de Associações usará o arquivo api.xml como uma diretriz para gerar as classes de wrapper C# necessárias. O conteúdo deste arquivo XML é uma variação do formato Android Open Source Project do Google. O snippet a seguir é um exemplo do conteúdo de 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>
Neste exemplo, api.xml declara uma classe no android
pacote chamado Manifest
que estende o java.lang.Object
.
Em muitos casos, a assistência humana é necessária para fazer com que a API Java pareça mais "semelhante ao .NET" ou para corrigir problemas que impedem a compilação do assembly de associação. Por exemplo, pode ser necessário alterar nomes de pacotes Java para namespaces .NET, renomear uma classe ou alterar o tipo de retorno de um método.
Essas alterações não são alcançadas modificando api.xml diretamente. Em vez disso, as alterações são registradas em arquivos XML especiais fornecidos pelo modelo Java Binding Library. Ao compilar o assembly de associação do Xamarin.Android, o Gerador de Associações será influenciado por esses arquivos de mapeamento ao criar o assembly de associação
Esses arquivos de mapeamento XML podem ser encontrados na pasta Transforms do projeto:
MetaData.xml – Permite que alterações sejam feitas na API final, como alterar o namespace da associação gerada.
EnumFields.xml – Contém o mapeamento entre constantes Java
int
e C#enums
.EnumMethods.xml – Permite alterar parâmetros de método e tipos de retorno de constantes Java
int
para C#enums
.
O arquivo MetaData.xml é o mais importante desses arquivos, pois permite alterações de uso geral na associação, como:
Renomear namespaces, classes, métodos ou campos para que eles sigam as convenções do .NET.
Remover namespaces, classes, métodos ou campos que não são necessários.
Movendo classes para namespaces diferentes.
Adicionar classes de suporte adicionais para fazer com que o design da associação siga os padrões do .NET Framework.
Vamos passar a discutir Metadata.xml com mais detalhes.
Metadata.xml Transformar arquivo
Como já aprendemos, o arquivo Metadata.xml é usado pelo Gerador de Associações para influenciar a criação do assembly de associação. O formato de metadados usa a sintaxe XPath e é quase idêntico aos metadados GAPI descritos no guia de metadados GAPI. Essa implementação é quase uma implementação completa do XPath 1.0 e, portanto, suporta itens no padrão 1.0. Esse arquivo é um poderoso mecanismo baseado em XPath para alterar, adicionar, ocultar ou mover qualquer elemento ou atributo no arquivo de API. Todos os elementos de regra na especificação de metadados incluem um atributo de caminho para identificar o nó ao qual a regra deve ser aplicada. As regras são aplicadas na seguinte ordem:
- add-node – Anexa um nó filho ao nó especificado pelo atributo path.
- attr – Define o valor de um atributo do elemento especificado pelo atributo path.
- remove-node – Remove os nós que correspondem a um XPath especificado.
Veja a seguir um exemplo de um arquivo Metadata.xml :
<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>
A seguir, listamos alguns dos elementos XPath mais comumente usados para as APIs Java:
interface
– Usado para localizar uma interface Java. Por exemplo:/interface[@name='AuthListener']
.class
– Usado para localizar uma classe . Por exemplo:/class[@name='MapView']
.method
– Usado para localizar um método em uma classe ou interface Java. Por exemplo:/class[@name='MapView']/method[@name='setTitleSource']
.parameter
– Identifique um parâmetro para um método. Por exemplo:/parameter[@name='p0']
Adição de tipos
O add-node
elemento informará ao projeto de associação Xamarin.Android para adicionar uma nova classe wrapper a api.xml. Por exemplo, o snippet a seguir direcionará o Gerador de Associação para criar uma classe com um construtor e um único campo:
<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>
Removendo tipos
É possível instruir o Gerador de Associações do Xamarin.Android a ignorar um tipo Java e não associá-lo. Isso é feito adicionando um remove-node
elemento XML ao arquivo metadata.xml :
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
Renomeando membros
A renomeação de membros não pode ser feita editando diretamente o arquivo api.xml porque o Xamarin.Android requer os nomes JNI (Interface Nativa Java) originais. Portanto, o //class/@name
atributo não pode ser alterado; se for, a associação não funcionará.
Considere o caso em que queremos renomear um tipo, android.Manifest
.
Para fazer isso, podemos tentar editar diretamente api.xml e renomear a classe assim:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="name">NewName</attr>
Isso resultará na criação do Gerador de Associações do seguinte código C# para a classe wrapper:
[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }
Observe que a classe wrapper foi renomeada para NewName
, enquanto o tipo Java original ainda Manifest
é . Não é mais possível que a classe de associação Xamarin.Android acesse nenhum método em android.Manifest
; a classe wrapper está associada a um tipo Java inexistente.
Para alterar corretamente o nome gerenciado de um tipo encapsulado (ou método), é necessário definir o managedName
atributo conforme mostrado neste exemplo:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
Renomeando EventArg
classes de wrapper
Quando o gerador de associação Xamarin.Android identifica um onXXX
método setter para um tipo de ouvinte, um evento C# e EventArgs
uma subclasse serão gerados para dar suporte a uma API com sabor de .NET para o padrão de ouvinte baseado em Java. Como exemplo, considere a seguinte classe e método Java:
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
O Xamarin.Android removerá o prefixo on
do método setter e, em vez disso, usará 2DSignNextManuever
como base para o nome da EventArgs
subclasse. A subclasse terá um nome semelhante a:
NavigationManager.2DSignNextManueverEventArgs
Esse não é um nome de classe C# legal. Para corrigir esse problema, o autor da associação deve usar o argsType
atributo e fornecer um nome C# válido para a EventArgs
subclasse:
<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
interface[@name='NavigationManager.Listener']/
method[@name='on2DSignNextManeuver']"
name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>
Atributos suportados
As seções a seguir descrevem alguns dos atributos para transformar APIs Java.
tipo de args
Esse atributo é colocado em métodos setter para nomear a EventArg
subclasse que será gerada para suportar ouvintes Java. Isso é descrito com mais detalhes abaixo na seção Renomeando classes de wrapper EventArg mais adiante neste guia.
eventName
Especifica um nome para um evento. Se estiver vazio, inibe a geração de eventos. Isso é descrito com mais detalhes no título da seção Renomeando classes de wrapper EventArg.
nome_gerenciado
Isso é usado para alterar o nome de um pacote, classe, método ou parâmetro. Por exemplo, para alterar o nome da classe MyClass
Java para NewClassName
:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
O exemplo a seguir ilustra uma expressão XPath para renomear o método java.lang.object.toString
para Java.Lang.Object.NewManagedName
:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
tipo gerenciado
managedType
é usado para alterar o tipo de retorno de um método. Em algumas situações, o Gerador de Associações inferirá incorretamente o tipo de retorno de um método Java, o que resultará em um erro de tempo de compilação. Uma solução possível nessa situação é alterar o tipo de retorno do método.
Por exemplo, o Gerador de Ligações acredita que o método de.neom.neoreadersdk.resolution.compareTo()
Java deve retornar um int
e tomar Object
como parâmetros, o que resulta na mensagem de erro Erro CS0535: 'DE. Neom.Neoreadersdk.Resolution' não implementa o membro da interface 'Java.Lang.IComparable.CompareTo(Java.Lang.Object)'.
O snippet a seguir demonstra como alterar o tipo do primeiro parâmetro do método C# gerado de a DE.Neom.Neoreadersdk.Resolution
para 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
Altera o tipo de retorno de um método. Isso não altera o atributo de retorno (pois as alterações nos atributos de retorno podem resultar em alterações incompatíveis na assinatura JNI). No exemplo a seguir, o tipo de retorno do método é alterado de para IAppendable
(lembre-se de SpannableStringBuilder
que o append
C# não dá suporte a tipos de retorno covariantes):
<attr path="/api/package[@name='android.text']/
class[@name='SpannableStringBuilder']/
method[@name='append']"
name="managedReturn">Java.Lang.IAppendable</attr>
Ofuscado
As ferramentas que ofuscam bibliotecas Java podem interferir no Gerador de Associação do Xamarin.Android e em sua capacidade de gerar classes wrapper C#. As características das classes ofuscadas incluem:
- O nome da classe inclui um $, ou seja , a$.class
- O nome da classe é totalmente comprometido com caracteres minúsculos, ou seja, a.class
Este snippet é um exemplo de como gerar um tipo C# "não ofuscado":
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
Esse atributo pode ser usado para alterar o nome de uma propriedade gerenciada.
Um caso especializado de uso propertyName
envolve a situação em que uma classe Java tem apenas um método getter para um campo. Nessa situação, o Gerador de Associação gostaria de criar uma propriedade somente gravação, algo que é desencorajado no .NET. O snippet a seguir mostra como "remover" as propriedades do .NET definindo o propertyName
como uma cadeia de caracteres vazia:
<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>
Observe que os métodos setter e getter ainda serão criados pelo Gerador de Ligações.
remetente
Especifica qual parâmetro de um método deve ser o sender
parâmetro quando o método é mapeado para um evento. O valor pode ser true
ou false
. Por exemplo:
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
visibility
Esse atributo é usado para alterar a visibilidade de uma classe, método ou propriedade. Por exemplo, pode ser necessário promover um protected
método Java para que seu wrapper C# correspondente seja 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 e EnumMethods.xml
Há casos em que as bibliotecas do Android usam constantes inteiras para representar estados que são passados para propriedades ou métodos das bibliotecas. Em muitos casos, é útil associar essas constantes inteiras a enumerações em C#. Para facilitar esse mapeamento, use os arquivos EnumFields.xml e EnumMethods.xml em seu projeto de associação.
Definindo uma enumeração usando EnumFields.xml
O arquivo EnumFields.xml contém o mapeamento entre as constantes Java int
e o C# enums
. Vejamos o seguinte exemplo de uma enumeração C# sendo criada para um conjunto de int
constantes:
<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>
Aqui, pegamos a classe SKRealReachSettings
Java e definimos uma enumeração C# chamada SKMeasurementUnit
no namespace Skobbler.Ngx.Map.RealReach
. As field
entradas definem o nome da constante Java (exemplo UNIT_SECOND
), o nome da entrada de enumeração (exemplo Second
) e o valor inteiro representado por ambas as entidades (exemplo 0
).
Definindo métodos Getter/Setter usando EnumMethods.xml
O arquivo EnumMethods.xml permite alterar os parâmetros do método e retornar tipos de constantes Java int
para C# enums
. Em outras palavras, ele mapeia a leitura e a gravação de enumerações C# (definidas no arquivo EnumFields.xml) para a constante get
e set
os métodos Javaint
.
Dada a SKRealReachSettings
enumeração definida acima, o seguinte arquivo EnumMethods.xml definiria o getter/setter para essa enumeração:
<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>
A primeira method
linha mapeia o valor de retorno do método Java getMeasurementUnit
para a SKMeasurementUnit
enumeração. A segunda method
linha mapeia o primeiro parâmetro do para a setMeasurementUnit
mesma enumeração.
Com todas essas alterações em vigor, você pode usar o código a seguir no Xamarin.Android para definir o MeasurementUnit
:
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
Resumo
Este artigo discutiu como o Xamarin.Android usa metadados para transformar uma definição de API do formato AOSP do Google. Depois de abordar as alterações possíveis usando Metadata.xml, ele examinou as limitações encontradas ao renomear membros e apresentou a lista de atributos XML suportados, descrevendo quando cada atributo deve ser usado.