• .NET 8 中 Android 资源生成的改进和变化
  • 发布于 2个月前
  • 124 热度
    0 评论
  • 弄潮儿
  • 1 粉丝 30 篇博客
  •   
随着 .NET 8 的发布,我们引入了一个新系统,用于生成访问 Android 资源的 C# 代码。在 Xamarin.Android、.NET 6 和 .NET 7 中生成 Resource.designer.cs 文件的系统已经被弃用。新系统生成一个名为 _Microsoft.Android.Resource.Designer 程序集。其中包含每个程序集的所有最终资源类。

什么是 Android 资源?
所有 Android 应用程序都包含一些用户界面资源。它们通常具有 XML 文件形式,包含用户界面布局、png 或 svg 文件形式的图像和图标以及包含样式和主题等内容的值。请参阅 Google 文档以深入了解 Android 资源。
 
Android构建过程的一部分是使用android sdk工具aapt2将这些资源编译成二进制形式。为了访问这些资源,android 公开了一个 API,它允许您传递一个整数 id 来检索资源。
SetContentView (2131492864);
作为 aapt2 构建过程的一部分,将生成文件 R.txt,其中包含从资源的“string”名称到 Id 的映射。例如,layout/Main.xml 可能映射到 id 2131492864。为了从 C# 访问此数据,我们需要一种在代码中公开这些数据的方法。这是由项目 $(RootNamespace) 中的 Resource 类处理的。我们从 R.txt 中获取值并将它们公开在这个类中。在 .NET 7 及之前版本的系统中,该类被写入 Resource.designer.cs 文件。它允许用户不需要硬编码Id就可以编写可维护的代码。所以上面的调用实际上看起来像这样:
SetContentView (Resource.Layout.Main);
Resource.Id.Main 将映射到 aapt2 生成的 Id。
文档:https://developer.android.com/guide/topics/resources/providing-resources
aapt2:https://developer.android.com/tools/aapt2

为什么要制定这个新系统?
旧系统存在一些影响应用程序大小和启动性能的问题。在旧系统中,每个 Android 程序集都有自己的一组Resource类。所以我们实际上到处都有重复的代码。因此,如果您在项目中使用 AndroidX,则引用 AndroidX 的每个程序集都会有一个像下面的Resource设计器 Id 类: 
public class Resource {
    public class Id {
        // aapt resource value: 0x7F0A0005
        public const int seekBar = 2131361797;
        // aapt resource value: 0x7F0A0006
        public const int menu = 2131361798;
    }
}
该代码将在每个库中重复。可能还有其他类,例如Layout/Menu/Style,都包含这些重复的代码。此外,每个Resource类都需要在运行时更新以获得正确的值。这是因为只有当我们构建最终应用程序并生成 R.txt 文件时,我们才知道每个资源的Id。因此应用程序Resource类是唯一具有正确 Id的类。

旧系统使用了名为 UpdateIdValues 的方法,该方法在启动时调用。该方法将遍历所有库项目并更新资源 Id以匹配应用程序中的资源 Id。根据应用程序的尺寸,这可能会导致严重的启动延迟。下面是该方法中的代码示例:
public static void UpdateIdValues()
{
    // 堆代码 duidaima.com
    global::Library.Resource.Id.seekBar = global::Foo.Foo.Resource.Id.seekBar;
    global::Library.Resource.Id.menu = global::Foo.Foo.Resource.Id.menu;
}
更糟糕的是,由于UpdateIdValues代码的存在,修剪器无法删除这些类中的任何一个。因此,即使应用程序只使用了一个或两个字段,所有这些类都会被保留。新系统对所有这些进行了重新设计,以使其适应修剪器,几乎以上显示的所有代码都不再生成。,甚至根本不需要 UpdateIdValues 调用。这将改善应用程序的大小和启动时间。 

这个新系统是如何运作的?
默认情况下,.NET 8 Android将 MSBuild 属性 $(AndroidUseDesignerAssembly) 设置为 true,完全关闭旧系统。重新启用旧系统需要手动将此属性更改为 false。新系统依赖于解析 aapt2 在构建过程中生成的 R.txt 文件。在运行 C# 编译器之前,将解析 R.txt 文件并生成新的程序集。该程序集将保存在IntermediateOutputPath 中,并且它会自动添加到应用程序或库的References列表中。

对于库项目,我们生成引用程序集而不是完整程序集。这向编译器发出信号,表明该程序集将在运行时被替换。(引用程序集是包含程序级 ReferenceAssemblyAttribute 的程序集。)

对于应用程序项目,我们生成完整的程序集作为 UpdateAndroidResources 目标的一部分。这确保我们使用的是 R.txt 文件中的最终值。这个最终的程序集将使用最终的包进行部署。

除了程序集之外,还将生成源文件 __Microsoft.Android.Resource.Designer.cs,如果您使用 F#,源文件为 __Microsoft.Android.Resource.Designer.fs。它包含一个从 Resource 类派生的类。它将存在于项目的 $(RootNamespace) 中。这是使现有代码能够正常工作的纽带 。因为Resource类的命名空间不会改变。对于应用程序项目,项目 RootNamespace 中的 Resource 类将从设计器程序集中的 ResourceConstants 类派生。这是为了保持与旧的Resource.designer.cs文件在应用程序项目中的工作方式的向后兼容性 。

测试表明我们可以将启动时间缩短约 8%。整体封装尺寸大约减少 2%-4%。

程序集:https://learn.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies
ReferenceAssemblyAttribute:https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.referenceassemblyattribute?view=net-7.0

我的 NuGet 包仍然有效吗?
有些人可能担心通过此更改,现有的包引用将停止工作。不用担心,新系统引入了一个修剪步骤,它将会升级旧系统的程序集引用以使用新系统。这将作为构建的一部分自动完成。此修剪步骤分析所有程序集中的 IL,查找使用旧 Resource.designer 字段的位置。然后,它将更新这些地方以使用新的Designer程序集属性。它还将完全删除该程序集中的旧Resource.designer。因此,即使您使用旧软件包,您仍然可以使用这个新系统。

链接器步骤应该涵盖访问 Resource.designer.cs 字段的几乎所有代码。但是,如果您遇到问题,请在 https://github.com/xamarin/xamarin-android/issues/new/choose 上提交问题。这个功能将适用于 net8.0-android 之前的任何 Android 程序集引用。使用新系统构建的包不能与以前版本的.NET Android 一起使用。如果您需要支持 .NET 7 或 Classic Xamarin.Android,请考虑使用多目标定位。

NuGet 包作者
如果您正在维护包含 Android 资源的 NuGet 包,如果是的话,您将需要进行一些更改。首先,不需要随 NuGet 一起提供新的 _Microsoft.Android.Resource.Designer.dll。它将由使用 NuGet 的应用程序在构建时生成。

新系统与 Classic Pre .NET Xamarin.Android 以及 .NET 6/7 Android 软件包不兼容。因此,如果您想继续支持 Classic Xamarin.Android 以及 .NET 8,您将需要对程序集进行多目标操作。如果您不再需要支持 Xamarin.Android 类,您可以将项目升级到 .NET Sdk Style 项目并使用以下内容:
<TargetFrameworks>net7.0-android;net8.0-android</TargetFrameworks>
Classic Xamarin.Android 将于明年停止支持,所以这可能是最佳选择。

如果您需要支持这两个系统,您可以使用Xamarin.Legacy.Sdk来同时支持 Xamarin.Android 和 net8.0-android。Xamarin.Legacy.Sdk是不受支持的,所以它只能作为用户升级到 .NET 8 时的权宜之计。有关如何使用此包的详细信息,请参阅 Xamarin.Legacy.Sdk GitHub 站点 https://github.com/xamarin/Xamarin.Legacy.Sdk。

从 .NET 6 android 开始,AndroidResource、AndroidAsset、AndroidEnvironment、AndroidJavaLibrary、EmbeddedNativeLibrary 和 AndroidNativeLibrary 项不再打包在程序集中。而是在构建时会生成一个 .aar 文件,其中包含这些数据,并命名为与程序集相同的名称。为了正常工作,需要将.aar 文件与程序集一起发送到 NuGet 中。如果不包含.aar,在运行时将会出现资源丢失错误,例如:
System.MissingMethodException: 'Method not found: int .Style.get_MyTheme()'
如果您在项目中使用 dotnet pack 并在 csproj 中指定 NuGet 属性和设置,则默认情况下会包含 .aar。但是,如果您使用 .nuspec,则需要手动将 .aar 文件添加到要包含的文件列表中。与.aar文件和嵌入文件相关的更改在OneDotNetEmbeddedResources.md中有文档记录。
Xamarin.Legacy.Sdk
https://devblogs.microsoft.com/dotnet/android-resource-designer-dotnet-8/

OneDotNetEmbeddedResources.md
https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/OneDotNetEmbeddedResources.md

总结
因此,新系统会导致软件包大小略微缩小,并且启动时间更快。您在应用程序中使用的资源越多,影响就越大。
用户评论