Compartilhar via


Metadados de associações de Java

Importante

No momento, estamos investigando o uso de associação personalizada na plataforma Xamarin. Faça esta pesquisa para informar os esforços futuros de desenvolvimento.

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 no JNI (Java Native Interface). O Xamarin.Android fornece uma ferramenta que gera essas associações. Essa ferramenta permite 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 que os metadados dão suporte e explica como resolve 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 existente do Android 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. Esta lista de APIs é armazenada em um arquivo XML que pode ser encontrado em {diretório do projeto}\obj\Release\api.xml para um build RELEASE e em {diretório de projeto}\obj\Debug\api.xml para um build de DEBUG .

Local do arquivo api.xml na pasta obj/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 desse arquivo XML é uma variação do formato projeto de software livre android 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 se sinta mais ".NET like" ou para corrigir problemas que impedem a compilação do assembly de associação. Por exemplo, pode ser necessário alterar nomes de pacote Java para namespaces .NET, renomear uma classe ou alterar o tipo de retorno de um método.

Essas alterações não são obtidas modificando api.xml diretamente. Em vez disso, as alterações são registradas em arquivos XML especiais fornecidos pelo modelo da Biblioteca de Associação Java. Ao compilar o assembly de associação 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 Transformações 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 retornar tipos de constantes Java int para C# enums .

O arquivoMetaData.xml é a maior importação desses arquivos, pois permite alterações de uso geral na associação, como:

  • Renomeando namespaces, classes, métodos ou campos para que eles sigam convenções do .NET.

  • Removendo namespaces, classes, métodos ou campos que não são necessários.

  • Movendo classes para namespaces diferentes.

  • Adicionar classes de suporte adicionais para tornar o design da associação seguir padrões do .NET Framework.

Vamos seguir em frente para discutir Metadata.xml mais detalhadamente.

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, dá suporte a 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 – acrescenta 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 nós correspondentes a um XPath especificado.

Veja a seguir um exemplo de um arquivo deMetadata.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>

O seguinte lista alguns dos elementos XPath mais comumente usados para a API 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 ao 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 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 (Java Native Interface) 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 da seguinte maneira:

<attr path="/api/package[@name='android']/class[@name='Manifest']" 
    name="name">NewName</attr>

Isso fará com que o Gerador de Associações crie o 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 quaisquer métodos no 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 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 .NET para o padrão de ouvinte baseado em Java. Por 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 será nomeada algo semelhante a:

NavigationManager.2DSignNextManueverEventArgs

Este 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 com suporte

As seções a seguir descrevem alguns dos atributos para transformar APIs Java.

argsType

Esse atributo é colocado em métodos setter para nomear a EventArg subclasse que será gerada para dar suporte a ouvintes Java. Isso é descrito mais detalhadamente abaixo na seção Renomeando classes de wrapper EventArg mais adiante neste guia.

eventName

Especifica um nome para um evento. Se estiver vazio, ele inibe a geração de eventos. Isso é descrito com mais detalhes no título da seção Renomeando Classes de Wrapper EventArg.

managedName

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>

managedType

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 Associaçõ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 um DE.Neom.Neoreadersdk.Resolution para um 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 append método é alterado de SpannableStringBuilder para IAppendable (lembre-se de que o 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 na 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 de 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 desejaria criar uma propriedade somente gravação, algo que não é recomendado 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 Associaçõ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 ele seja o wrapper C# correspondente: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 de inteiro para representar estados que são passados para propriedades ou métodos das bibliotecas. Em muitos casos, é útil associar essas constantes de inteiro 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 constantes Java int e C# enums. Vamos usar 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, usamos 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 parâmetros de 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 métodos e set constantes get Javaint.

Considerando a SKRealReachSettings enumeração definida acima, o seguinte arquivo EnumMethods.xml definiria o getter/setter para esta 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 retornado do método Java getMeasurementUnit para a SKMeasurementUnit enumeração . A segunda method linha mapeia o primeiro parâmetro do setMeasurementUnit para a 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 abranger as alterações possíveis usando Metadata.xml, ele examinou as limitações encontradas ao renomear membros e apresentou a lista de atributos XML com suporte, descrevendo quando cada atributo deve ser usado.