Partilhar via


Migração de projeto de associação 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 Associação de Biblioteca Java do Android com o mesmo nome do projeto de associação do Xamarin.Android:

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

    A abertura do arquivo de projeto confirmará que você tem um projeto no estilo do 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 o seu Arquivo Java (JAR) ou Arquivo Android (AAR) ao projeto e verifique se a 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 propriedade $(AndroidClassParser). class-parse agora é a opção padrão e a única com suporte.

class-parse aproveita muitos novos recursos modernos não disponíveis em jar2xml, como:

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

AndroidCodegenTarget

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

Se você tiver um código associado à mão em seus arquivos Additions que interage com o código de associação gerado, talvez seja necessário atualizar para ser compatível com XAJavaInterop1.

Inclusão de arquivo padrão

Dada a seguinte estrutura de arquivo:

Transforms/
    Metadata.xml
foo.jar

Os arquivos Transforms\*.xml são incluídos automaticamente como um item @(TransformFile), e os arquivos .jar/.aar são incluídos automaticamente como um item @(AndroidLibrary). Isso associará os tipos C# para 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. Este comportamento pode ser desabilitado para itens Android definindo a propriedade $(EnableDefaultAndroidItems) como false, ou todo o comportamento de inclusão de item padrão pode ser desabilitado definindo a propriedade $(EnableDefaultItems) como false.

Arquivos indesejados .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 ser associado 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 este 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 os arquivos .jar e .aar. No Xamarin.Android, foram usados os seguintes grupos de itens, que podem usar metadados de item para obter o mesmo resultado:

Grupo de itens herdados Novo Grupo de Itens Metadados de item Tipo de projeto herdado
AndroidAarLibrary AndroidLibrary Bind="false" Aplicativo
AndroidJavaLibrary AndroidLibrary Bind="false" Aplicativo ou biblioteca de classes
EmbeddedJar AndroidLibrary N/D Projeto de associação
EmbeddedReferenceJar AndroidLibrary Bind="false" Projeto de associação
InputJar AndroidLibrary Pack="false" Projeto de associação
LibraryProjectZip AndroidLibrary N/D Projeto de associação

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

<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 .apk ou .aab resultante. 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 verificado quanto ao 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 qualquer arquivo .jar ou .aar encontrados no mesmo diretório que um .dll referenciado.

Se um projeto fizer 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>, o .jar/.aar deverá 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 interface, o que é um padrão comum em Java:

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

Anteriormente, este padrão tinha suporte criando uma class alternativa contendo 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, isto significa que a classe alternativa da qual o código existente pode depender não é mais gerada.

A configuração da propriedade $(AndroidBoundInterfacesContainConstants) como false no arquivo de projeto será revertida para o comportamento herdado.

Tipos de interface aninhados

Tradicionalmente, o C# não permite que tipos aninhados sejam declarados em um interface, o 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.

A configuração da propriedade $(AndroidBoundInterfacesContainTypes) como false no arquivo de projeto será revertida para o comportamento herdado.

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 nível interface usando metadata para definir o atributo unnest. 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 membros static e métodos default:

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 () { ... }
}

Os métodos de interface default tradicionalmente não são associados, pois eles não são necessários e não existia 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.

A configuração da propriedade $AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods como false no arquivo de projeto será revertida para o comportamento herdado.

Tipos de referência anuláveis

O suporte para tipos de referência anuláveis (NRT) foi adicionado ao Xamarin.Android 11.0. O suporte a 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 do Xamarin.Android.

Resource.designer.cs

No Xamarin.Android, os projetos de associação Java não dão suporte à geração de um arquivo Resource.designer.cs. Como os projetos de associação são apenas bibliotecas de classes no .NET, este arquivo será gerado. Esta pode ser uma alteração interruptiva 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 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 de projeto:

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