Compartilhar via


Migração de projeto de binding do Xamarin.Android

No .NET, não há nenhum conceito de um projeto de associação do Android como um tipo de projeto separado. Qualquer um dos grupos de itens do MSBuild ou ações de build que funcionam em projetos de associação do Xamarin.Android tem suporte por meio de um aplicativo ou biblioteca do .NET para Android.

Para migrar uma biblioteca de associação do Xamarin.Android para uma biblioteca de classes do .NET para Android:

  1. No Visual Studio, crie um novo projeto de Vinculação de Biblioteca Java para Android com o mesmo nome do projeto de vinculação do Xamarin.Android.

    Captura de tela da criação de um projeto de Associação de Biblioteca Java do Android no Visual Studio.

    Abrir o arquivo de projeto confirmará que você tem um projeto no estilo SDK do .NET:

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0-android</TargetFramework>
        <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    </Project>
    

    Observação

    O arquivo de projeto de uma biblioteca de associação do Android é idêntico ao arquivo de projeto de uma biblioteca de classes do Android.

  2. Adicione seu Arquivo Java (JAR) ou AAR (Arquivo Android) ao projeto e verifique se sua ação de build está definida como AndroidLibrary.

  3. Copie todas as transformações ou adições da biblioteca de associações do Xamarin.Android.

Opções herdadas sem suporte

As opções herdadas a seguir não têm mais suporte. As alternativas com suporte estão disponíveis há vários anos e a opção de migração mais suave é atualizar e testar seus projetos atuais com essas opções antes de migrá-los para o .NET.

AndroidClassParser

jar2xml não é mais uma opção válida para a $(AndroidClassParser) propriedade. class-parse agora é a opção padrão e com suporte apenas.

class-parse toma vantagem de muitos novos recursos modernos, que não estão disponíveis em jar2xml, tais como:

  • Nomes de parâmetro automáticos para métodos de classe (se o código Java for compilado com javac -parameters).
  • Suporte do Kotlin.
  • Suporte ao DIM (membro de interface estático/padrão).
  • Suporte a anotações de tipo de referência anulável (NRT) em Java.

AndroidCodegenTarget

XamarinAndroid não é mais uma opção válida para a $(AndroidCodegenTarget) propriedade. XAJavaInterop1 agora é a opção padrão e com suporte apenas.

Se você tiver código manualmente vinculado nos seus arquivos Additions que interage com o código de associação gerado, talvez precise ser atualizado para compatibilidade com XAJavaInterop1.

Inclusão de arquivo padrão

Dada a seguinte estrutura de arquivo:

Transforms/
    Metadata.xml
foo.jar

Transforms\*.xml os arquivos são incluídos automaticamente como um @(TransformFile) item e .jar/.aar os arquivos são incluídos automaticamente como um @(AndroidLibrary) item. Isso irá associar tipos C# com os tipos Java encontrados em foo.jar usando as correções de metadados de Transforms\Metadata.xml.

O comportamento padrão de mascaramento de arquivo relacionado ao Android é definido em AutoImport.props. Esse comportamento pode ser desabilitado para itens Android definindo a propriedade $(EnableDefaultAndroidItems) como false, ou o comportamento de inclusão padrão de todos os itens pode ser desabilitado definindo a propriedade $(EnableDefaultItems) como false.

Arquivos indesejados, como .jar ou .aar, podem ser incluídos com os caracteres curinga padrão. Por exemplo, os seguintes erros do compilador C# resultam de um arquivo AndroidStudio\gradle\wrapper\gradle-wrapper.jar vinculado involuntariamente:

Org.Gradle.Cli.AbstractCommandLineConverter.cs(11,89): error CS0535: 'Download' does not implement interface member 'IDownload.Download(URI, File)'
Org.Gradle.Wrapper.Download.cs(10,60): error CS0535: 'AbstractCommandLineConverter' does not implement interface member 'ICommandLineConverter.Convert(ParsedCommandLine, Object)'

Para resolver esse problema, você pode remover o arquivo específico no arquivo de projeto:

<ItemGroup>
  <AndroidLibrary Remove="AndroidStudio\gradle\wrapper\gradle-wrapper.jar" />
</ItemGroup>

Como alternativa, você pode excluir todos os arquivos em uma pasta:

<AndroidLibrary Remove="AndroidStudio\**\*" />

Novos nomes de grupo de itens

<AndroidLibrary> agora é o grupo de itens recomendado a ser usado para todos .jar e .aar arquivos. No Xamarin.Android, foram usados os seguintes grupos de itens, que podem usar metadados de item para obter o mesmo resultado:

Grupo de Itens Legados Novo Grupo de Itens Metadados de item Tipo de projeto legado
AndroidAarLibrary AndroidLibrary Bind="false" Aplicação
AndroidJavaLibrary AndroidLibrary Bind="false" Aplicativo ou biblioteca de classes
EmbeddedJar AndroidLibrary Não disponível Projeto de vinculação
EmbeddedReferenceJar AndroidLibrary Bind="false" Projeto de vinculação
InputJar AndroidLibrary Pack="false" Projeto de vinculação
LibraryProjectZip AndroidLibrary Não disponível Projeto de vinculação

Considere um arquivo .aar ou .jar no qual você não está interessado em incluir uma vinculação C#. Isso é comum para casos em que você tem dependências Java ou Kotlin que você não precisa chamar do C#. Nesse caso, você pode definir os Bind metadados como false. Por padrão, o arquivo é captado pelos curingas padrão. Você também pode usar o Update atributo para definir os Bind metadados:

<ItemGroup>
  <AndroidLibrary Update="foo.jar" Bind="false">
</ItemGroup>

Em um projeto de biblioteca de classes Android, isso redistribuiria o arquivo .jar dentro do pacote NuGet resultante como está. Em um projeto de aplicativo Android, isso incluiria o arquivo .jar no arquivo resultante .apk ou .aab. Nenhum dos dois incluiria uma associação C# para esta biblioteca Java.

Arquivos JAR/AAR inseridos

No Xamarin.Android, o Java .jar ou .aar geralmente era inserido na associação .dll como um Recurso Inserido. No entanto, isso levou a builds lentos, pois cada .dll deve ser aberto e analisado para código Java. Se encontrado, ele deve ser extraído para o disco a ser usado.

No .NET, o código Java não está mais inserido no .dll. O processo de build do aplicativo incluirá automaticamente quaisquer arquivos .jar ou .aar encontrados no mesmo diretório que um .dll referenciado.

Quando um projeto faz referência a uma associação por meio de <PackageReference> ou <ProjectReference>, tudo funcionará e nenhuma consideração adicional será necessária. No entanto, se um projeto fizer referência a uma associação por meio de <Reference>, .jar/.aar deve estar localizado ao lado do .dll. Ou seja, para a seguinte referência:

<Reference Include="MyBinding.dll" />

Um diretório como o do exemplo a seguir não funcionará:

lib/
    MyBinding.dll

Em vez disso, o diretório também deve conter o código nativo:

lib/
    MyBinding.dll
    mybinding.jar

Considerações sobre migração

Há vários novos recursos definidos por padrão para ajudar a produzir associações que correspondam melhor aos seus equivalentes Java. No entanto, se você estiver migrando um projeto de associação existente, esses recursos poderão criar associações que não são compatíveis com a API com suas associações existentes. Para manter a compatibilidade, convém desabilitar ou modificar esses novos recursos.

Constantes de interface

Tradicionalmente, o C# não permite que constantes sejam declaradas em um interfacepadrão comum em Java:

public interface Foo {
     public static int BAR = 1;
}

Anteriormente, esse padrão era compatível com a criação de uma alternativa class que contém as constantes:

public abstract class Foo : Java.Lang.Object
{
   public static int Bar = 1;
}

Com o C# 8, essas constantes são colocadas no interface:

public interface IFoo
{
    public static int Bar = 1;
}

No entanto, isso significa que a classe alternativa da qual o código existente pode depender não é mais gerada.

Definir a propriedade $(AndroidBoundInterfacesContainConstants) como false no arquivo de projeto fará com que o comportamento legado seja restaurado.

Tipos de interface aninhados

Tradicionalmente, o C# não permite que tipos aninhados sejam declarados em um interface, que é permitido em Java:

public interface Foo {
     public class Bar { }
}

Este padrão foi suportado movendo o tipo aninhado para um tipo de nível superior com um nome gerado composto pela interface e pelo nome do tipo aninhado:

public interface IFoo { }

public class IFooBar : Java.Lang.Object { }

Com o C# 8, os tipos aninhados podem ser colocados no interface:

public interface IFoo
{
    public class Bar : Java.Lang.Object { }
}

No entanto, isso significa que a classe de nível superior da qual o código existente pode depender não é mais gerada.

Definir a propriedade $(AndroidBoundInterfacesContainTypes) como false no arquivo de projeto fará com que o comportamento legado seja restaurado.

Se você quiser usar uma abordagem híbrida, por exemplo, para manter os tipos aninhados existentes movidos para um tipo de nível superior, mas permitir que quaisquer tipos aninhados futuros permaneçam aninhados, você poderá especificar isso no interface nível usando metadata para definir o unnest atributo. Defini-lo como true resultará em "cancelar o aninhamento" de qualquer tipo aninhado (comportamento herdado):

<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">true</attr>

Defini-lo como false resultará em tipos aninhados que permanecem aninhados no interface (comportamento do .NET):

<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">false</attr>

Usando esta abordagem, você pode deixar a propriedade $(AndroidBoundInterfacesContainTypes) como true e definir unnest como true para cada interface um com tipos aninhados que você tem atualmente. Eles sempre permanecerão tipos de nível superior, enquanto todos os novos tipos aninhados introduzidos posteriormente serão aninhados.

Membros de interface estáticos e padrão (DIM)

Tradicionalmente, o C# não permite que as interfaces contenham static membros e default métodos:

public interface Foo {
  public static void Bar () { ... }
  public default void Baz () { ... }
}

Os membros estáticos nas interfaces têm suporte ao movê-los para um irmão class:

public interface IFoo { }

public class Foo
{
    public static void Bar () { ... }
}

default métodos de interface tradicionalmente não foram associados, pois eles não são necessários e não havia um constructo C# para dar suporte a eles.

Com o C# 8, os membros static e default têm suporte em interfaces, espelhando a interface Java:

public interface IFoo
{
    public static void Bar () { ... }
    public default void Baz () { ... }
}

No entanto, isso significa que o irmão alternativo class que contém static membros não será mais gerado.

Definir a propriedade $AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods como false no arquivo de projeto fará com que o comportamento legado seja restaurado.

Tipos de referência anuláveis

O suporte para NRT (tipos de referência anuláveis) foi adicionado ao Xamarin.Android 11.0. O suporte ao NRT pode ser habilitado usando o mecanismo .NET padrão:

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Como o padrão para .NET é disable, o mesmo se aplica a projetos Xamarin.Android.

Resource.designer.cs

No Xamarin.Android, os projetos de associação java não dão suporte à geração de um Resource.designer.cs arquivo. Como os projetos de associação são apenas bibliotecas de classes no .NET, esse arquivo será gerado. Isso pode ser uma alteração significativa ao migrar projetos existentes.

Um exemplo de falha dessa alteração é se a associação gerar uma classe nomeada Resource no namespace raiz:

error CS0101: The namespace 'MyBinding' already contains a definition for 'Resource'

Ou, no caso do AndroidX, há arquivos de projeto com - no nome, como androidx.window/window-extensions.csproj. Isso resulta no namespace raiz window-extensions e em um C# inválido em Resource.designer.cs:

error CS0116: A namespace cannot directly contain members such as fields, methods or statements
error CS1514: { expected
error CS1022: Type or namespace definition, or end-of-file expected

Para desabilitar a geração Resource.designer.cs, defina a propriedade $(AndroidGenerateResourceDesigner) como false no arquivo do projeto.

<PropertyGroup>
  <AndroidGenerateResourceDesigner>false</AndroidGenerateResourceDesigner>
</PropertyGroup>