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 中没有的许多新式功能,例如:

  • 类方法的自动参数名称(如果使用 javac -parameters 编译 Java 代码)。
  • 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 不适用 绑定项目

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

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

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

嵌入式 JAR/AAR 文件

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

在 .NET 中,Java 代码不再嵌入到 .dll。 应用生成过程将自动包含在与引用的 .dll 位于同一目录中找到的任何 .jar.aar 文件。

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

<Reference Include="MyBinding.dll" />

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

lib/
    MyBinding.dll

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

lib/
    MyBinding.dll
    mybinding.jar

迁移注意事项

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

接口常量

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

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 将会还原为旧行为。

可为空引用类型

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>