.NET 8 中 Android 资源生成的改进和变化

作者:Dean Ellis

排版:Alan Wang

随着 .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 来检索资源。

csharp 复制代码
SetContentView (2131492864);

作为 aapt2 构建过程的一部分,将生成文件 R.txt,其中包含从资源的"string"名称到 Id 的映射。例如,layout/Main.xml 可能映射到 id 2131492864。为了从 C# 访问此数据,我们需要一种在代码中公开这些数据的方法。这是由项目 $(RootNamespace) 中的 Resource 类处理的。我们从 R.txt 中获取值并将它们公开在这个类中。在 .NET 7 及之前版本的系统中,该类被写入 Resource.designer.cs 文件。它允许用户不需要硬编码 Id 就可以编写可维护的代码。所以上面的调用实际上看起来像这样:

csharp 复制代码
SetContentView (Resource.Layout.Main);

Resource.Id.Main 将映射到 aapt2 生成的 Id。

为什么要制定这个新系统?

旧系统存在一些影响应用程序大小和启动性能的问题。在旧系统中,每个 Android 程序集都有自己的一组 Resource 类。所以我们实际上到处都有重复的代码。因此,如果您在项目中使用 AndroidX,则引用 AndroidX 的每个程序集都会有一个像下面的 Resource 设计器 Id 类:

csharp 复制代码
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。根据应用程序的尺寸,这可能会导致严重的启动延迟。下面是该方法中的代码示例:

csharp 复制代码
public static void UpdateIdValues()
{
    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%。

我的 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 项目并使用以下内容:

csharp 复制代码
<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,在运行时将会出现资源丢失错误,例如:

csharp 复制代码
System.MissingMethodException: 'Method not found: int .Style.get_MyTheme()'

如果您在项目中使用 dotnet pack 并在 csproj 中指定 NuGet 属性和设置,则默认情况下会包含 .aar。但是,如果您使用 .nuspec,则需要手动将 .aar 文件添加到要包含的文件列表中。

与 .aar 文件和嵌入文件相关的更改在 OneDotNetEmbeddedResources.md 中有文档记录。

总结

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

相关推荐
alexhilton15 分钟前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw4 小时前
安卓图片性能优化技巧
android
风往哪边走5 小时前
自定义底部筛选弹框
android
Yyyy4825 小时前
MyCAT基础概念
android
Android轮子哥6 小时前
尝试解决 Android 适配最后一公里
android
雨白7 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
时光追逐者7 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 50 期(2025年8.11-8.17)
c#·.net·.netcore·.net core
风往哪边走7 小时前
自定义仿日历组件弹框
android
没有了遇见8 小时前
Android 外接 U 盘开发实战:从权限到文件复制
android
Monkey-旭9 小时前
Android 文件存储机制全解析
android·文件存储·kolin