使用 Xamarin 构建跨平台应用程序:Android 开发人员的视角
已发表: 2022-03-11一次编写代码并在多个平台上使用它一直是许多软件开发人员的梦想。 虽然这已经有一段时间了,但它总是以可维护性、易于测试甚至更糟糕的用户体验为代价。
使用原生 SDK 开发移动应用程序可能是所有扎根于桌面应用程序开发领域的开发人员的起点。 编程语言会成为一些人的障碍:如果有人在开发 Java 桌面或后端应用程序方面有经验,那么转到移动应用程序开发公司并使用 Android 会比在 iOS 上从头开始使用 Objective-C 容易得多。
我一直对跨平台应用程序开发持怀疑态度。 当性能很重要时,像 Sencha、Cordova、Titanium 等基于 JavaScript 的框架永远不会被证明是一个明智的选择。 这些框架缺乏 API 和古怪的用户体验。
但是后来,我遇到了 Xamarin。
在本文中,您将了解如何使用 Xamarin 在多个平台之间共享代码,而不会影响移动应用程序开发的任何其他方面。 本文将特别关注 Android 和 iOS,但您可以使用类似的方法添加对 Xamarin 支持的任何其他平台的支持。
什么是 Xamarin?
Xamarin 是一个开发平台,可让您使用 C# 和 .NET 为 iOS、Android 和 Windows Phone 编写跨平台但本机的应用程序。
Xamarin 提供与本机 Android 和 iOS API 的 C# 绑定。 这使您能够使用所有 Android 和 iOS 的本机用户界面、通知、图形、动画和其他电话功能——所有这些都使用 C#。
每个新版本的 Android 和 iOS 都与 Xamarin 相匹配,新版本包含对其新 API 的绑定。
Xamarin 的 .NET 端口包括数据类型、泛型、垃圾回收、语言集成查询 (LINQ)、异步编程模式、委托和 Windows Communication Foundation (WCF) 的子集等功能。 库是通过逗留来管理的,以仅包含引用的组件。
Xamarin.Forms 是其他 UI 绑定和 Windows Phone API 之上的一个层,它提供了一个完全跨平台的用户界面库。
编写跨平台应用程序
为了使用 Xamarin 编写跨平台应用程序,开发人员需要选择以下两种可用项目类型之一:
- 可移植类库 (PCL)
- 共享项目
PCL 允许您编写可在多个平台之间共享的代码,但有一个限制。 由于并非所有 .NET API 在所有平台上都可用,因此对于 PCL 项目,您将限制它在其目标平台上运行。
下表显示了哪些 API 在哪些平台上可用:
特征 | .NET 框架 | Windows 应用商店应用 | 银光 | 视窗电话 | 赛马林 |
---|---|---|---|---|---|
核 | 是 | 是 | 是 | 是 | 是 |
LINQ | 是 | 是 | 是 | 是 | 是 |
可查询的 | 是 | 是 | 是 | 7.5+ | 是 |
序列化 | 是 | 是 | 是 | 是 | 是 |
数据注释 | 4.0.3+ | 是 | 是 | 是 | 是 |
在构建过程中,PCL 被编译成单独的 DLL,并在运行时由 Mono 加载。 可以在运行时提供同一接口的不同实现。
另一方面,共享项目允许您为要支持的每个平台编写特定于平台的代码,从而为您提供更多控制权。 共享项目中的代码可以包含编译器指令,这些指令将启用或禁用代码段,具体取决于哪个应用程序项目正在使用该代码。
与 PCL 不同,共享项目不生成任何 DLL。 代码直接包含在最终项目中。
使用 MvvmCross 为您的跨平台代码提供结构
可重用的代码可以为开发团队节省金钱和时间。 但是,结构良好的代码使开发人员的生活更加轻松。 没有人比开发人员更欣赏编写良好的无错误代码。
Xamarin 本身提供了一种机制,使编写可重用的跨平台代码变得更加容易。
移动开发人员熟悉必须编写两次或更多次相同逻辑才能支持 iOS、Android 和其他平台的场景。 但是使用 Xamarin,如前一章所述,很容易重用为一个平台编写的代码,也可以用于其他一些平台。
那么 MvvmCross 在哪里出现呢?
顾名思义,MvvmCross 使得在 Xamarin 应用程序中使用 MVVM 模式成为可能。 它带有一堆库、API 和实用程序,在跨平台应用程序开发中非常方便。
MvvmCross 可以显着减少您在任何其他应用程序开发方法中编写的样板代码量(有时用不同的语言多次编写)。
MvvmCross 解决方案的结构
MvvmCross 社区推荐了一种非常简单有效的方式来构建 MvvmCross 解决方案:
<ProjectName>.Core <ProjectName>.UI.Droid <ProjectName>.UI.iOS
MvvmCross 解决方案中的核心项目与可重用代码有关。 Core 项目是一个 Xamarin PCL 项目,主要关注可重用性。
任何用 Core 编写的代码都应该尽可能地与平台无关。 它应该只包含可以在所有平台上重用的逻辑。 Core 项目不得使用任何 Android 或 iOS API,也不得访问任何特定于任何平台的内容。
业务逻辑层、数据层和后端通信都是包含在 Core 项目中的完美候选。 通过视图层次结构(活动、片段等)的导航将在核心中实现。
在继续之前,有必要了解一种架构设计模式,这对于理解 MvvmCross 及其工作原理至关重要。 从名字就可以看出,MvvmCross 很大程度上依赖于 MVVM 模式。
MVVM 是一种架构设计模式,有助于将图形用户界面与业务逻辑和后端数据分离。
这种模式在 MvvmCross 中是如何使用的?
好吧,既然我们想要实现代码的高度可重用性,我们希望在我们的 Core 中拥有尽可能多的内容,这是一个 PCL 项目。 由于视图是代码中唯一不同平台的部分,我们不能跨平台重用它们。 该部分在与平台相关的项目中实现。
MvvmCross 使我们能够使用 ViewModels 从核心编排应用程序导航。
了解了基础知识和技术细节后,让我们通过创建自己的 MvvmCross Core 项目开始使用 Xamarin:
创建 MvvmCross 核心项目
打开 Xamarin Studio 并创建一个名为ToptalExampleSolution
的解决方案:
由于我们正在创建一个 Core 项目,因此坚持命名约定是一个好主意。 确保将Core
后缀添加到项目名称中。
为了获得 MvvmCross 支持,需要将 MvvmCross 库添加到我们的项目中。 要补充一点,我们可以在 Xamarin Studio 中使用对 NuGet 的内置支持。
要添加库,请右键单击 Packages 文件夹并选择Add Packages...选项。
在搜索栏,我们可以搜索 MvvmCross,它会过滤掉与 MvvmCross 相关的结果,如下图:
单击“添加包”按钮会将其添加到项目中。
将 MvvmCross 添加到我们的项目后,我们就可以编写核心代码了。
让我们定义我们的第一个 ViewModel。 为了创建一个,创建文件夹的层次结构,如下所示:
以下是每个文件夹的内容:
- 模型:代表真实状态内容的领域模型
- 服务:保存我们的服务(业务逻辑、数据库等)的文件夹
- ViewModel:我们与模型通信的方式
我们的第一个 ViewModel 叫做FirstViewModel.cs
public class FirstViewModel : MvxViewModel { private string _firstName; private string _lastName; private string _fullName; public string FirstName { get { return _firstName; } set { _lastName = value; RaisePropertyChanged(); } } public string LastName { get { return _lastName; } set { _lastName = value; RaisePropertyChanged(); } } public string FullName { get { return _fullName; } set { _fullName = value; RaisePropertyChanged(); } } public IMvxCommand ConcatNameCommand { get { return new MvxCommand(() => { FullName = $"{FirstName} {LastName}"; }); } public IMvxCommand NavigateToSecondViewModelCommand { get { return new MvxCommand(() => { ShowViewModel<SecondViewModel>(); }); } } }
现在我们有了第一个 ViewModel,我们可以创建第一个视图并将事物绑定在一起。
安卓用户界面
为了显示 ViewModel 的内容,我们需要创建一个 UI。
创建 Android UI 的第一步是在当前解决方案中创建一个 Android 项目。 为此,右键单击解决方案名称并选择Add -> Add New Project... 。 在向导中,选择 Android 应用程序并确保将项目命名为ToptalExample.UI.Droid
。
如前所述,我们现在需要为 Android 添加 MvvmCross 依赖项。 为此,请按照与 Core 项目相同的步骤添加 NuGet 依赖项。
添加 MvvmCross 依赖项后,需要添加对 Core 项目的引用,以便我们可以使用那里编写的代码。 要添加对 PCL 项目的引用,请右键单击 References 文件夹并选择Edit References...选项。 在 Projects 选项卡上,选择之前创建的 Core 项目并单击 OK。
下一部分可能有点难以理解。
现在我们必须告诉 MvvmCross 它应该如何设置我们的应用程序。 为此,我们必须创建一个Setup
类:
namespace ToptalExample.UI.Droid { public class Setup : MvxAndroidSetup { public Setup(Context context) : base(context) { } protected override IMvxApplication CreateApp() { return new Core.App(); } } }
从类中可以看出,我们是根据Core.App
实现告诉MvvmCross CreateApp
,这是Core中定义的一个类,如下图所示:
public class App : MvxApplication { public override void Initialize() { RegisterAppStart(new AppStart()); } } public class AppStart : MvxNavigatingObject, IMvxAppStart { public void Start(object hint = null) { ShowViewModel<FirstViewModel>(); } }
在App
类中,我们正在创建AppStart
的一个实例,它将显示我们的第一个 ViewModel。

现在唯一剩下的就是创建一个将被 MvvmCross 绑定的 Android Layout 文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FirstName" /> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text LastName" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FullName" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click ConcatNameCommand" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click NavigateToSecondViewModelCommand" /> </LinearLayout>
在布局文件中,我们有 MvvmCross 自动解析的绑定。 对于EditText
,我们正在为 Text 属性创建一个绑定,这将是一个双向绑定。 从 ViewModel 端调用的任何更改都将自动反映在视图上,反之亦然。
View
类可以是一个活动或一个片段。 为简单起见,我们使用一个加载给定布局的活动:
[Activity(Label = "ToptalExample.UI.Droid", Theme = "@style/Theme.AppCompat", MainLauncher = true, Icon = "@mipmap/icon")] public class MainActivity : MvxAppCompatActivity<FirstViewModel> { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); } }
对于第一个按钮,我们有一个命令绑定,这意味着当我们单击按钮时,MvvmCross 将从 ViewModel 调用ContactNameCommand
。
对于第二个按钮,我们将展示另一个 ViewModel。
iOS 用户界面
创建一个 iOS 项目与创建一个 Android 项目并没有什么不同。 您需要按照类似的步骤来添加新项目,只是这一次,而不是 Android,只需创建一个 iOS 项目。 只要确保你保持命名约定一致。
添加iOS项目后,需要添加MvvmCross iOS的依赖。 步骤与 Core 和 Android 完全相同(在您的 iOS 项目中右键单击 References 并单击Add References... )。
现在,就像我们为 Android 所做的那样,需要创建一个Setup
类,它将告诉 MvvmCross 如何设置我们的应用程序。
public class Setup : MvxIosSetup { public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter) : base(appDelegate, presenter) { } protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp() { return new App(); } }
请注意, Setup
类现在扩展了 MvxIosSetup ,对于 Android,它正在扩展MvxAndroidSetup 。
这里的一个补充是我们必须改变我们的AppDelegate
类。
iOS 上的AppDelegate
负责启动用户界面,因此我们必须说明视图将如何在 iOS 上呈现。 您可以在此处了解有关演示者的更多信息。
[Register("AppDelegate")] public class AppDelegate : MvxApplicationDelegate { // class-level declarations public override UIWindow Window { get; set; } public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { Window = new UIWindow(UIScreen.MainScreen.Bounds); var presenter = new MvxIosViewPresenter(this, Window); var setup = new Setup(this, presenter); setup.Initialize(); var startup = Mvx.Resolve<IMvxAppStart>(); startup.Start(); Window.MakeKeyAndVisible(); return true; } }
为了展示我们的 VIewModel,我们需要创建一个视图。 对于这种情况,让我们通过右键单击项目并选择Add -> New File并从 iOS 部分选择 ViewController 创建一个 ViewController,我们将其命名为 FirstViewController。
Xamarin 创建了三个文件,我们将在其中定义绑定的内容。 与 Android 不同,对于 iOS,我们必须通过代码以不同的方式定义我们的绑定(尽管我们也可以在 Android 上这样做,并且在某些情况下,需要这样做)。
当需要在视图之间导航时,它是通过 ViewModel 完成的。 在命令NavigateToSecondViewModelCommand
中,方法ShowViewModel<SecondViewModel>()
将找到适当的视图并导航到它。
但是,MVVMCross 是如何知道要加载哪个视图的呢?
这没有任何魔法。 当我们为 Android(Activity 或 Fragment)创建视图时,我们正在扩展具有类型参数的基类之一( MvxAppCompatActivity<VM>
)。 当我们调用ShowViewMolel<VM>
时,MvvmCross 会查找一个View
,该 View 使用类型参数VM
扩展了Activity
或Fragment
类。 这就是为什么不允许同一个 ViewModel 有两个视图类的原因。
控制反转
由于 Xamarin 仅提供围绕本机 API 的 C# 包装器,因此它不提供任何形式的依赖注入 (DI) 或控制反转 (IoC) 机制。
如果没有运行时依赖注入或编译时注入,就不容易创建松散耦合、可重用、可测试和易于维护的组件。 IoC 和 DI 的概念早已为人所知。 关于 IoC 的详细信息可以在许多在线文章中找到。 您可以从 Martin Fowler 的介绍性文章中了解有关这些模式的更多信息。
IoC 从 MvvmCrosses 的早期版本开始就可用,它允许在应用程序启动时和需要时在运行时注入依赖项。
为了获得松耦合的组件,我们永远不应该要求类的具体实现。 要求具体实现限制了在运行时更改实现行为的能力(您不能用另一个实现替换它)。 这使得测试这些组件变得困难。
出于这个原因,我们将声明一个接口,我们将为其提供一个具体的实现。
public interface IPasswordGeneratorService { string Generate(int length); }
和实施:
public class PasswordGeneratorService : IPasswordGeneratorService { public string Generate(int length) { var val; var res = new StringBuilder(); var rnd = new Random(); while (0 < length--) { res.Append(valid[rnd.Next(valid.Length)]); } return res.ToString(); } }
我们的 ViewModel 现在可以要求我们负责提供的接口IPasswordGenerationService
的实例。
为了让 MvvmCross 在运行时注入PasswordGeneratorService
实现,我们需要告诉 MvvmCross 使用哪个实现。 如果我们想在两个平台上使用一个实现,我们可以在App.cs
中注册实现,在应用程序注册之后:
public override void Initialize() { RegisterAppStart(new AppStart()); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, PasswordGeneratorService>(); }
上面对静态方法LazyConstructAndRegisterSingleton<TInterface, TType>
的调用注册了要注入的实现。 此方法注册适当的实现,但不创建对象。
该对象仅在需要时创建,并且仅在它注册为单例后才创建一次。
如果我们想立即创建一个单例对象,可以通过调用Mvx.RegisterSingleton<TInterface>()
来实现。
在某些情况下,我们不想在我们的应用程序中只有单例。 我们的对象可能不是线程安全的,或者我们希望始终拥有一个新实例可能有其他原因。 如果是这种情况,MvvmCross 提供了方法Mvx.RegisterType<TInterface,TType>()
,它可以用于注册实现,以便在需要时实例化一个新实例。
如果您需要为每个平台提供单独的具体实现,您始终可以在特定于平台的项目中这样做:
public class DroidPasswodGeneratorService : IPasswordGeneratorService { public string Generate(int length) { return "DroidPasswordGenerator"; } }
而我们实现的注册是在 Droid 项目下的Setup.cs
类中完成的:
protected override void InitializePlatformServices() { base.InitializePlatformServices(); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, DroidPasswodGeneratorService>(); }
PCL 代码初始化后,MvvmCross 将调用InitializePlatformServices
并注册我们平台特定的服务实现。
当我们注册多个单例实现时,MvvmCross 将只使用最后注册的实现。 所有其他注册都将被丢弃。
使用 Xamarin 构建跨平台应用程序
在本文中,您已了解 Xamarin 如何允许您跨不同平台共享代码,同时仍保持应用程序的本机感觉和性能。
MvvmCross 提供了另一个抽象层,进一步增强了使用 Xamarin 构建跨平台应用程序的体验。 MVVM 模式提供了一种创建所有平台通用的导航和用户交互流的方法,使您需要编写的特定于平台的代码数量仅限于视图。
我希望这篇文章给了你一个了解 Xamarin 的理由,并激励你用它构建你的下一个跨平台应用程序。