作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Emran Bajrami的头像

Emran Bajrami

埃姆兰是一个注重结果的人, articulate, 以及善于分析的Android和Java工程师,能够跳出固有思维模式.

Previously At

BMW Group
Share

编写一次代码并在多个平台上使用它一直是许多软件开发人员的梦想. 虽然这可能已经有一段时间了, 它总是以可维护性为代价, ease of testing, or even worse, poor user experience.

使用本机SDK开发移动应用程序可能是所有在桌面应用程序开发领域扎根的开发人员的起点. 编程语言将成为一些人的障碍:如果有人在开发Java桌面或后端应用程序方面有经验, moving to a 移动应用开发公司 在Android上工作比在iOS上从头开始使用Objective-C要容易得多.

我一直对跨平台应用程序开发持怀疑态度. 基于javascript的框架,如Sencha、Cordova、Titanium等. 当性能很重要时,永远不要证明这是一个明智的选择. 这些框架缺乏api和古怪的用户体验.

但后来,我遇到了Xamarin.

Xamarin跨平台开发

In this article, 您将学习如何使用Xamarin跨多个平台共享代码,而不会影响移动应用程序开发的任何其他方面. 本文将特别关注Android和iOS, 但是您可以使用类似的方法添加对Xamarin支持的任何其他平台的支持.

What Is Xamarin?

Xamarin是一个开发平台 它允许你为iOS编写跨平台的原生应用程序, Android, 和Windows Phone, c#和 .NET.

Xamarin为原生Android和iOS api提供c#绑定. 这让你能够使用所有Android和iOS的原生用户界面, notifications, graphics, animation, 以及其他手机功能——全部使用c#.

Android和iOS的每一个新版本都与Xamarin相匹配, 发布了一个包含新api绑定的新版本.

Xamarin’s port of .NET包括数据类型等特性, generics, garbage collection, 语言集成查询(LINQ), 异步编程模式, delegates, 和Windows通信基础(WCF)的子集. 库是用一个逗留符来管理的,它只包含被引用的组件.

Xamarin.Forms是位于其他UI绑定和Windows Phone API之上的一层, 哪个提供了一个完全跨平台的用户界面库.

The scope of Xamarin

编写跨平台应用程序

为了用Xamarin编写跨平台的应用程序, 开发商需要从两种可用的项目类型中选择一种:

  • 便携式类库(PCL)
  • Shared Project

PCL允许您编写可以在多个平台之间共享的代码, but with one limitation. Since not all .. NET api在所有平台上都可用, with a PCL project, 您将限制它只能在目标平台上运行.

Xamarin的联系和局限

下表显示了哪些api在哪些平台上可用:

Feature.NET FrameworkWindows Store AppsSilverlightWindows PhoneXamarin
CoreYYYYY
LINQYYYYY
IQueryableYYY7.5+Y
SerializationYYYYY
Data Annotations4.0.3+YYYY

在构建过程中,PCL被编译成单独的dll,并在运行时由Mono加载. 可以在运行时期间提供同一接口的不同实现.

On the other hand, 共享项目允许您为想要支持的每个平台编写特定于平台的代码,从而为您提供了更多的控制权. 共享项目中的代码可以包含编译器指令,这些指令将根据正在使用代码的应用程序项目启用或禁用代码部分.

与PCL不同,共享项目不产生任何DLL. 代码直接包含在最终项目中.

用MvvmCross给你的跨平台代码结构

可重用代码可以为开发团队节省金钱和时间. 然而,结构良好的代码使开发人员的工作变得容易得多. 没有人比开发人员更欣赏编写良好的无bug代码.

Xamarin本身提供了一种机制,使编写可重用的跨平台代码变得更加容易.

手机开发者很熟悉这样的场景:为了支持iOS,他们必须编写两次或更多次相同的逻辑, Android, and other platforms. But with Xamarin, 如前一章所述, 为一个平台编写的代码很容易在其他平台上重用.

那么MvvmCross在哪里发挥作用呢?

MvvmCross, 正如名字所暗示的那样, 使得在Xamarin应用程序中使用MVVM模式成为可能. 它附带了一堆库, APIs, 以及在跨平台应用程序开发中非常方便的实用程序.

MvvmCross可以显著减少您在任何其他应用程序开发方法中编写的样板代码的数量(有时使用不同的语言多次编写).

MvvmCross方案的结构

MvvmCross社区推荐了一种非常简单有效的构建MvvmCross解决方案的方法:

.Core
.UI.Droid
.UI.iOS

MvvmCross解决方案中的核心项目与可重用代码相关. 核心项目是一个Xamarin PCL项目,其主要焦点是可重用性.

任何用Core编写的代码都应该尽可能与平台无关. 它应该只包含可以在所有平台上重用的逻辑. Core项目不得使用任何Android或iOS API,也不得访问任何特定于任何平台的任何内容.

The business logic layer, data layer, 和后端通信都是Core项目的理想选择. 通过视图层次(活动、片段等)进行导航.)将在核心地区实现.

Before continuing, 有必要了解一个架构设计模式,它对于理解MvvmCross及其工作原理至关重要. 从名称可以看出,MvvmCross在很大程度上依赖于MVVM模式.

MVVM是一种架构设计模式,它有助于将图形用户界面与业务逻辑和后端数据分离.

这个模式如何在MvvmCross中使用?

Well, 因为我们想要实现代码的高可重用性, 我们希望在我们的核心中拥有尽可能多的东西, which is a PCL project. 因为视图是代码中唯一一个平台与另一个平台不同的部分, 我们不能跨平台重用它们. 该部分在与平台相关的项目中实现.

MvvmCross structure

MvvmCross让我们能够使用ViewModels从核心中编排应用程序导航.

基础知识和技术细节都解决了, 让我们通过创建我们自己的MvvmCross核心项目来开始Xamarin:

创建MvvmCross核心项目

打开Xamarin Studio并创建一个名为 ToptalExampleSolution:

Creating the solution

由于我们正在创建一个Core项目,因此坚持使用命名约定是一个好主意. Make sure the Core 后缀被添加到项目名称中.

为了获得MvvmCross支持,需要在我们的项目中添加MvvmCross库. 为了补充,我们可以在Xamarin Studio中使用内置的NuGet支持.

要添加库,右键单击Packages文件夹并选择 Add Packages… option.

In the search field, 我们可以搜索MvvmCross, 它将过滤出与MvvmCross相关的结果,如下所示:

Filtering results

Clicking on the Add Package 按钮将其添加到项目中.

随着MvvmCross添加到我们的项目中,我们准备编写我们的核心代码.

让我们定义第一个ViewModel. 为了创建一个文件夹,创建如下层次结构的文件夹:

推荐的文件夹层次结构

以下是每个文件夹的内容:

  • Models: 表示真实状态内容的领域模型
  • Services: 存放我们的服务(业务逻辑、数据库等)的文件夹.)
  • ViewModel: 我们与模型沟通的方式

我们的第一个ViewModel被调用 FirstViewModel.cs

    公共类FirstViewModel: MvxViewModel
    {
        私有字符串_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();
                });
            }
        }
    }

现在我们有了第一个ViewModel,我们可以创建第一个视图并将它们绑定在一起.

Android UI

为了显示ViewModel的内容,我们需要创建一个UI.

创建Android UI的第一步是在当前解决方案中创建一个Android项目. 为此,右键单击解决方案名称并选择 Add -> Add New Project…. 在向导中,选择Android app并确保为项目命名 ToptalExample.UI.Droid.

如前所述,我们现在需要为Android添加MvvmCross依赖项. 要做到这一点,请按照与Core项目相同的步骤添加NuGet依赖项.

添加MvvmCross依赖项后, 它需要添加一个引用到我们的核心项目,这样我们就可以使用我们在那里编写的代码. 要向PCL项目添加引用,请右键单击References文件夹并选择 Edit References… option. 在Projects选项卡上,选择之前创建的Core项目并单击OK.

向PCL项目添加引用

接下来的部分可能有点难以理解.

现在我们必须告诉MvvmCross它应该如何设置我们的应用程序. 为了做到这一点,我们必须创建一个 Setup class:

namespace ToptalExample.UI.Droid
{
    公共类安装:MvxAndroidSetup
    {
        public Setup(Context)
            : base(context)
        {
        }

        CreateApp()
        {
            return new Core.App();
        }
    }
}

从课堂上可以看出,我们正在告诉MvvmCross CreateApp based on the Core.App 这是一个在Core中定义的类,如下所示:

    公共类App: MvxApplication
    {
        public override void Initialize()
        {
            RegisterAppStart(新AppStart ());
        }
    }

    公共类AppStart: MvxNavigatingObject, IMvxAppStart
    {
        public void Start(对象提示= null)
        {
            ShowViewModel();
        }
    }

In the App 类,我们正在创建的实例 AppStart,它将显示我们的第一个ViewModel.

现在唯一剩下的事情就是创建一个Android布局文件,它将被MvvmCross绑定:



    
    
    
    

在布局文件中,我们有由MvvmCross自动解析的绑定. For EditText,我们正在为Text属性创建一个绑定,这将是一个双向绑定. 从ViewModel端调用的任何更改都将自动反映在视图上,反之亦然.

The View 类可以是活动或片段. 为了简单起见,我们使用一个activity来加载给定的布局:

[活动(Label = "ToptalExample ..UI., Theme = "@style/Theme., MainLauncher = true, Icon = "@mipmap/ Icon ")]
    public class MainActivity : MvxAppCompatActivity
    {
        OnCreate(Bundle Bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);

        }
    }

For the first button, 我们有一个命令绑定,这意味着当我们点击按钮MvvmCross将调用 ContactNameCommand from the ViewModel.

对于第二个按钮,我们将显示另一个ViewModel.

iOS UI

创建iOS项目与创建Android项目并没有什么不同. 添加新项目需要遵循类似的步骤, only this time, instead of Android, 只需要创建一个iOS项目. 只要确保您保持命名约定的一致性即可.

添加iOS项目后,需要为MvvmCross iOS添加依赖项. 步骤与Core和Android完全相同(右键单击iOS项目中的References,然后点击) Add References…).

现在,正如我们对Android所做的那样,需要创建一个 Setup 类,它将告诉MvvmCross如何设置我们的应用程序.

公共类设置:MvxIosSetup
    {
        (MvxApplicationDelegate, appDelegate, IMvxIosViewPresenter, presenter)
            : base(appDelegate, presenter)
        {
        }

        保护覆盖MvvmCross.Core.ViewModels.IMvxApplication CreateApp ()
        {
            return new App();
        }
    }

Note that the Setup class now extends MvxIosSetup 对于Android来说,它正在扩展 MvxAndroidSetup.

另外一点是,我们必须改变我们的 AppDelegate class.

AppDelegate 在iOS上负责启动用户界面, 所以我们需要告诉视图将如何在iOS上呈现. 你可以了解更多关于演讲者的信息 here.

(注册(“AppDelegate”))
    AppDelegate: MvxApplicationDelegate
    {
        //类级别声明

        公共覆盖UIWindow窗口
        {
            get;
            set;
        }

        uiapplicationapplicationnsdictionary启动选项
        {
            Window = new UIWindow(uisscreen).MainScreen.Bounds);

            var presenter = new MvxIosViewPresenter(this, Window);

            var setup = new setup (this, presenter);
            setup.Initialize();

            var startup = Mvx.Resolve();
            startup.Start();

            Window.MakeKeyAndVisible();

            return true;
        }
}

为了显示VIewModel,我们需要创建一个视图. 对于这种情况,让我们通过右键单击项目并选择来创建一个ViewController Add -> New File 并从iOS section中选择ViewController,我们将其命名为FirstViewController.

Xamarin创建了三个文件,我们将在其中定义绑定. Unlike Android, for iOS, 我们必须以不同的方式定义绑定, 通过代码(虽然我们也可以在Android上这样做), for some cases, it is required to do so).

当需要在视图之间导航时,可以通过ViewModel完成. In the command NavigateToSecondViewModelCommand, the method ShowViewModel() 会找到合适的视图并导航到它吗.

但是,MVVMCross如何知道加载哪个视图?

这里面没有任何魔法. 当我们为Android (Activity或Fragment)创建视图时,我们正在扩展一个带有类型参数的基类(MvxAppCompatActivity). When we call ShowViewMolel, MvvmCross查找a View which extends Activity or Fragment 带有类型参数的类 VM. 这就是为什么不允许为同一个ViewModel拥有两个视图类的原因.

Inversion of Control

因为Xamarin只是提供了原生api周围的c#包装器, 它不提供任何形式的依赖注入(DI)或控制反转(IoC)机制.

无需运行时注入依赖项或编译时注入, 创建松耦合并不容易, reusable, testable, 以及易于维护的组件. The idea of IoC and DI has been known for a really long time; details about IoC can be found in many articles online. 您可以从以下链接了解更多关于这些模式的信息 Martin Fowler的介绍性文章.

从MvvmCrosses的早期版本开始,就提供了IoC, 它还允许在应用程序启动时以及需要时注入依赖项.

为了得到松散耦合的组件, 我们永远不应该要求类的具体实现. 要求具体的实现限制了在运行时更改实现行为的能力(您不能用另一个实现替换它). 这使得测试这些组件变得困难.

For that reason, 我们将声明一个接口,我们将为它提供一个具体的实现.

公共接口IPasswordGeneratorService
{
     字符串生成(int length);
}

And implementation:

    PasswordGeneratorService
    {
        公共字符串生成(int长度)
        {
            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(新AppStart ());

            Mvx.LazyConstructAndRegisterSingleton();
        }

上面的静态方法调用 LazyConstructAndRegisterSingleton 注册要注入的实现. 此方法注册适当的实现,但不创建对象.

对象只在需要的时候创建,而且只创建一次,因为它被注册为单例.

如果我们想立即创建一个单例对象,可以通过调用 Mvx.RegisterSingleton().

在某些情况下,我们不希望应用程序中只有单例. 我们的对象可能不是线程安全的,或者可能有其他原因使我们希望总是有一个新实例. 如果是这种情况,MvvmCross提供方法 Mvx.RegisterType(), 哪一个可以用来注册实现,在需要的时候实例化一个新的实例.

以防您需要为每个平台提供单独的具体实现, 你可以在特定于平台的项目中这样做:

ippasswordgeneratorservice: ippasswordgeneratorservice
    {
        公共字符串生成(int长度)
        {
            返回“DroidPasswordGenerator”;
        }
    }

我们实现的注册是在 Setup.cs 类在Droid项目下:

InitializePlatformServices()
        {
            base.InitializePlatformServices ();

            Mvx.LazyConstructAndRegisterSingleton();

        }

初始化PCL代码后,MvvmCross将调用 InitializePlatformServices 并注册我们平台特定的服务实现.

当我们注册多个单例实现时, MvvmCross将只使用最后注册的实现. 所有其他注册将被丢弃.

使用Xamarin构建跨平台应用程序

In this article, 您已经看到Xamarin如何允许您跨不同平台共享代码,同时仍然保持应用程序的原生感觉和性能.

MvvmCross提供了另一个抽象层,进一步增强了使用Xamarin构建跨平台应用程序的体验. MVVM模式提供了一种创建所有平台通用的导航和用户交互流的方法, 将需要编写的特定于平台的代码数量限制在视图中.

我希望本文能给您提供一个了解Xamarin的理由,并激励您使用它构建下一个跨平台应用程序.

聘请Toptal这方面的专家.
Hire Now
Emran Bajrami的头像
Emran Bajrami

Located in 萨拉热窝,波斯尼亚-黑塞哥维那联邦,波斯尼亚-黑塞哥维那

Member since January 14, 2016

About the author

埃姆兰是一个注重结果的人, articulate, 以及善于分析的Android和Java工程师,能够跳出固有思维模式.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

BMW Group

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.