Xamarin.Android 绑定项目迁移

在 .NET 中,没有将 Android 绑定项目作为单独的项目类型的概念。 通过适用于 Android 应用或库的 .NET 支持在 Xamarin.Android 绑定项目中运行的任何 MSBuild 项组或生成操作。

将 Xamarin.Android 绑定库迁移到适用于 Android 的 .NET 类库:

  1. 在 Visual Studio 中,创建与 Xamarin.Android 绑定项目同名的新 Android Java 库绑定项目:

    在 Visual Studio 中创建 Android Java 库绑定项目的屏幕截图。

    打开项目文件将确认你有一个 .NET SDK 样式的项目:

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

    注释

    Android 绑定库的项目文件与 Android 类库的项目文件相同。

  2. 将 Java 存档(JAR)或 Android 存档(AAR)添加到项目,并确保其生成作设置为 AndroidLibrary

  3. 从您的 Xamarin.Android 绑定库中复制任何转换或添加。

不支持的旧选项

不再支持以下旧选项。 支持的替代方法已推出几年,最流畅的迁移选项是在将当前项目迁移到 .NET 之前使用这些选项更新和测试当前项目。

AndroidClassParser

jar2xml 不再是属性 $(AndroidClassParser) 的有效选项。 class-parse 现在是默认选项,仅受支持选项。

class-parse 利用许多在 jar2xml 中不可用的新式功能,例如:

  • 类方法的自动参数名称(如果您的 Java 代码经过 javac -parameters 编译)。
  • Kotlin 支持。
  • 静态/默认接口成员 (DIM) 支持。
  • Java 可为 Null 引用类型 (NRT) 批注支持。

AndroidCodegenTarget

XamarinAndroid 不再是属性 $(AndroidCodegenTarget) 的有效选项。 XAJavaInterop1 现在是默认选项,仅受支持选项。

如果 Additions 文件中有与生成的绑定代码交互的手动绑定代码,则可能需要更新它以兼容 XAJavaInterop1

默认文件包含

给定以下文件结构:

Transforms/
    Metadata.xml
foo.jar

Transforms\*.xml文件会自动作为@(TransformFile)项包含在内,而.jar/.aar文件会自动作为@(AndroidLibrary)项包含在内。 这将使用 Transforms\Metadata.xml 中的元数据修复来绑定 foo.jar 中找到的 Java 类型的 C# 类型。

AutoImport.props 中定义了默认的 Android 相关文件通配行为。 通过将属性设置为 $(EnableDefaultAndroidItems)false 可禁用 Android 项的此行为,或者通过将属性设置为 $(EnableDefaultItems)false 来禁用所有默认项包含行为。

默认通配符可能会包含不需要的.jar.aar文件。 例如,以下 C# 编译器错误导致 AndroidStudio\gradle\wrapper\gradle-wrapper.jar 文件无意绑定:

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)'

若要解决此问题,可以在项目文件中删除特定文件:

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

或者,可以排除文件夹中的所有文件:

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

新建项组名称

<AndroidLibrary> 现在是推荐用于所有 .jar.aar 文件的项目组。 在 Xamarin.Android 中,使用了以下项组,可以改用项元数据来实现相同的结果:

遗留项目组 新建项目组 项目元数据 旧项目类型
AndroidAarLibrary AndroidLibrary Bind="false" 应用程序
AndroidJavaLibrary AndroidLibrary Bind="false" 应用程序或类库
EmbeddedJar AndroidLibrary 不适用 绑定项目
EmbeddedReferenceJar AndroidLibrary Bind="false" 绑定项目
InputJar AndroidLibrary Pack="false" 绑定项目
LibraryProjectZip AndroidLibrary 不适用 绑定项目

请考虑一个 .aar.jar 文件,其中你对包括 C# 绑定不感兴趣。 对于不需要从 C# 调用的 Java 或 Kotlin 依赖项的情况,这种情况很常见。 在这种情况下,可以将元数据设置为 Bindfalse。 默认情况下,文件由默认通配符选取。 还可以使用 Update 特性设置 Bind 元数据:

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

在 Android 类库项目中,这会将 .jar 文件按原样重新分发到结果 NuGet 包中。 在 Android 应用程序项目中,.jar 文件会包括在生成的 .apk 文件或 .aab 文件中。 这两个库都不会包含此 Java 库的 C# 绑定。

嵌入式 JAR/AAR 文件

在 Xamarin.Android 中,Java .jar.aar 通常作为嵌入资源嵌入到绑定 .dll 中。 但是,这会导致构建速度缓慢,因为必须打开并扫描每个 .dll 来查找 Java 代码。 如果找到,则必须将其提取到要使用的磁盘。

在 .NET 中,Java 代码不再嵌入到 ..dll 应用程序构建过程会自动包含在与所引用的 .dll 相同的目录中找到的所有 .jar.aar 文件。

如果项目通过 <PackageReference><ProjectReference> 引用绑定,那么一切正常运行,并且不需要其他注意事项。 但是,如果项目通过<Reference>引用绑定,.aar.jar/则必须位于该.dll绑定的旁边。 也就是说,对于以下引用:

<Reference Include="MyBinding.dll" />

以下示例中的目录将不起作用:

lib/
    MyBinding.dll

相反,目录还必须包含本机代码:

lib/
    MyBinding.dll
    mybinding.jar

迁移注意事项

默认情况下会设置多个新功能,以帮助生成更好地匹配其 Java 对应项的绑定。 但是,如果要迁移现有绑定项目,这些功能可能会创建与现有绑定不兼容 API 的绑定。 为了保持兼容性,你可能希望禁用或修改这些新功能。

接口常量

传统上,C# 不允许在 Java 中常见的模式中 interface声明常量:

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

以前通过创建包含常量的替代项class来支持此模式。

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

使用 C# 8 时,这些常量放置在以下位置 interface

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

但是,这意味着现有代码可能依赖的替代类不再生成。

在项目文件中将$(AndroidBoundInterfacesContainConstants)属性设置为false以恢复旧行为。

嵌套接口类型

传统上,C# 不允许在 interface 中声明嵌套类型,这在 Java 中是允许的。

public interface Foo {
     public class Bar { }
}

通过将嵌套类型移动为顶级类型,并使用由接口和嵌套类型名称组成的生成名称来实现对该模式的支持:

public interface IFoo { }

public class IFooBar : Java.Lang.Object { }

在 C# 8 中,嵌套类型可以放置在 interface

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

但是,这意味着现有代码可能依赖的顶级类不再生成。

在项目文件中将 $(AndroidBoundInterfacesContainTypes) 属性设置为 false 时,会还原为旧行为。

例如,如果您希望使用混合方法,例如将现有的嵌套类型保持为已移动到顶级类型,但允许任何未来的嵌套类型继续保持嵌套,您可以在interface级别使用metadata来设置unnest属性。 将其设置为 true 将会导致“取消嵌套”任何嵌套类型(旧行为):

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

将其设置为 false 将导致嵌套类型仍嵌套在 interface (.NET 行为):

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

使用此方法,可以将 $(AndroidBoundInterfacesContainTypes) 属性保留为 true,以及使用你目前拥有的嵌套类型为每个 interfaceunnest 设置为 true。 这些类型将始终保持顶级类型,而以后引入的任何新嵌套类型都将作为嵌套类型存在。

静态和默认接口成员 (DIM)

传统上,C# 不允许接口包含 static 成员和 default 方法:

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

通过将接口上的静态成员移至同级 class 对其提供支持:

public interface IFoo { }

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

default 接口方法传统上没有绑定,因为它们不是必需的,并且没有 C# 构造来支持它们。

使用 C# 8,接口支持 staticdefault 成员,类似于 Java 接口。

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

但是,这意味着包含static成员的备用同级class将不再生成。

在项目文件中将$AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods属性设置为false,将恢复到旧行为。

可为 Null 的引用类型

Xamarin.Android 11.0 中添加了对可为 Null 的引用类型 (NRT) 的支持。 可以使用标准 .NET 机制启用 NRT 支持:

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

因为 .NET disable的默认值是,因此同样适用于 Xamarin.Android 项目。

Resource.designer.cs

在 Xamarin.Android 中,Java 绑定项目不支持生成 Resource.designer.cs 文件。 由于绑定项目只是 .NET 中的类库,因此将生成此文件。 迁移现有项目时,这可能是一项重大更改。

此更改失败的一个示例是,如果绑定在根命名空间中生成名为 Resource 的类:

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

或者,在 AndroidX 中,有项目文件 - 的名称,例如 androidx.window/window-extensions.csproj。 这会导致根命名空间 window-extensionsResource.designer.cs 中的无效 C# 代码。

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

若要禁用 Resource.designer.cs 生成,请将 $(AndroidGenerateResourceDesigner) 属性设置为 false 项目文件中:

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