本文概述
一次编写代码并在多个平台上使用它一直是许多软件开发人员的梦想。尽管这已经有一段时间了, 但它总是以可维护性, 易于测试甚至更糟糕的用户体验为代价。
对于所有扎根桌面应用程序开发领域的开发人员而言, 使用本机SDK开发移动应用程序可能是起点。编程语言将成为某些人的障碍:如果有人在开发Java桌面或后端应用程序方面有丰富经验, 那么比起从头开始为iOS使用Objective-C迁移到移动应用程序开发公司并使用Android会容易得多。
我一直对跨平台应用程序开发持怀疑态度。当性能很重要时, Sencha, Cordova, Titanium等基于JavaScript的框架永远不会被证明是明智的选择。这些框架的确缺乏API和古怪的用户体验。
但是后来, 我遇到了Xamarin。
在本文中, 你将学习如何使用Xamarin在多个平台上共享代码, 而又不影响移动应用程序开发的任何其他方面。本文将特别关注Android和iOS, 但是你可以使用类似的方法添加对Xamarin支持的任何其他平台的支持。
你在给什么?
Xamarin是一个开发平台, 允许你使用C#和.NET为iOS, Android和Windows Phone编写跨平台(本机)的应用程序。
Xamarin提供对本机Android和iOS API的C#绑定。这使你能够使用C#来使用所有Android和iOS的本机用户界面, 通知, 图形, 动画以及其他电话功能。
Xamarin会匹配每个新发行的Android和iOS版本, 其中包括一个包含其新API绑定的新版本。
Xamarin的.NET端口包含数据类型, 泛型, 垃圾回收, 语言集成查询(LINQ), 异步编程模式, 委托和Windows Communication Foundation(WCF)子集等功能。可以持久管理库, 使其仅包含引用的组件。
Xamarin.Forms是其他UI绑定和Windows Phone API之上的一层, 它提供了一个完全跨平台的用户界面库。
编写跨平台应用程序
为了使用Xamarin编写跨平台应用程序, 开发人员需要选择两种可用项目类型之一:
- 可移植类库(PCL)
- 共享项目
PCL允许你编写可以在多个平台之间共享的代码, 但有一个限制。由于并非所有.NET API都可在所有平台上使用PCL项目, 因此你将限制它在其目标平台上运行。
下表显示了哪些API在哪些平台上可用:
特征 | .NET Framework | Windows应用商店 | 银光 | Windows Phone | Xamarin |
---|---|---|---|---|---|
核心 | 和 | 和 | 和 | 和 | 和 |
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解决方案中的核心项目与可重用代码有关。核心项目是Xamarin PCL项目, 主要关注可重用性。
用Core编写的任何代码都应尽可能地与平台无关。它只应包含可以在所有平台上重用的逻辑。 Core项目不得使用任何Android或iOS API, 也不得访问特定于任何平台的任何内容。
业务逻辑层, 数据层和后端通信都是包含在Core项目中的理想选择。通过视图层次结构(活动, 片段等)导航将在Core中实现。
在继续之前, 有必要了解一种架构设计模式, 这对于理解MvvmCross及其工作方式至关重要。从名称可以看出, MvvmCross很大程度上取决于MVVM模式。
MVVM是一种架构设计模式, 可促进将图形用户界面与业务逻辑和后端数据分离。
MvvmCross中如何使用此模式?
好吧, 由于我们希望实现代码的高度可重用性, 因此我们希望在PCL项目的Core中拥有尽可能多的资源。由于视图是代码的唯一组成部分, 因此在一个平台之间存在差异, 因此我们无法在多个平台之间重复使用它们。该部分在与平台相关的项目中实现。
MvvmCross使我们能够使用ViewModels从Core协调应用程序导航。
在不了解基础知识和技术细节的情况下, 让我们通过创建我们自己的MvvmCross Core项目开始使用Xamarin:
创建一个MvvmCross核心项目
打开Xamarin Studio并创建一个名为srcminiExampleSolution的解决方案:
由于我们正在创建一个Core项目, 因此最好遵循命名约定。确保将核心后缀添加到项目名称。
为了获得MvvmCross支持, 需要将MvvmCross库添加到我们的项目中。另外, 我们可以在Xamarin Studio中使用对NuGet的内置支持。
要添加库, 请右键单击Packages文件夹, 然后选择Add Packages…选项。
在搜索字段中, 我们可以搜索MvvmCross, 它将过滤掉与MvvmCross相关的结果, 如下所示:
单击添加包按钮将其添加到项目中。
将MvvmCross添加到我们的项目后, 我们就可以编写我们的Core代码了。
让我们定义第一个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, 我们可以创建第一个视图并将事物绑定在一起。
Android用户界面
要显示ViewModel的内容, 我们需要创建一个UI。
创建Android UI的第一步是在当前解决方案中创建一个Android项目。为此, 右键单击解决方案名称, 然后选择添加->添加新项目…。在向导中, 选择” Android应用”, 并确保将项目命名为srcminiExample.UI.Droid。
如前所述, 我们现在需要为Android添加MvvmCross依赖项。为此, 请按照与Core项目相同的步骤添加NuGet依赖项。
添加MvvmCross依赖项后, 需要添加对我们的Core项目的引用, 以便我们可以使用在此编写的代码。要添加对PCL项目的引用, 请右键单击”引用”文件夹, 然后选择”编辑引用…”选项。在”项目”选项卡上, 选择先前创建的Core项目, 然后单击”确定”。
下一部分可能很难理解。
现在我们必须告诉MvvmCross应该如何设置我们的应用程序。为此, 我们必须创建一个Setup类:
namespace srcminiExample.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布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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 = "srcminiExample.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项目并没有什么不同。你只需要这次类似的步骤来添加一个新项目, 只是这次创建一个iOS项目而不是Android。只要确保你保持命名约定一致即可。
添加iOS项目后, 你需要为MvvmCross iOS添加依赖项。步骤与Core和Android完全相同(右键单击iOS项目中的”引用”, 然后单击”添加引用…”)。
现在, 就像我们在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(活动或片段)创建视图时, 我们正在扩展具有类型参数(MvxAppCompatActivity <VM>)的基类之一。当我们调用ShowViewMolel <VM>时, MvvmCross查找一个View, 该View扩展了具有类型参数VM的Activity或Fragment类。这就是为什么不允许同一ViewModel具有两个视图类的原因。
控制反转
由于Xamarin仅提供围绕本机API的C#包装器, 因此它不提供任何形式的依赖项注入(DI)或控制反转(IoC)机制。
如果没有运行时注入依赖项或编译时注入, 就很难创建松耦合, 可重用, 可测试且易于维护的组件。 IoC和DI的想法已经存在了很长时间。有关IoC的详细信息可以在许多在线文章中找到。你可以从Martin Fowler的介绍性文章中进一步了解这些模式。
自MvvmCrosses的早期版本以来, IoC就已经可用, 并且它允许在运行应用程序时以及需要它们时在运行时注入依赖项。
为了获得松散耦合的组件, 我们永远不应要求类的具体实现。需要具体的实现限制了在运行时更改实现行为的能力(你不能将其替换为另一个实现)。这使得很难测试这些组件。
因此, 我们将声明一个接口, 为此我们将有一个具体的实现。
public interface IPasswordGeneratorService
{
string Generate(int length);
}
并实现:
public class PasswordGeneratorService : IPasswordGeneratorService
{
public string Generate(int length)
{
var valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
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的理由, 并激发你使用它构建下一个跨平台应用程序的动机。