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

Emran is a results-driven, articulate, 以及善于分析的Android和Java工程师,能够跳出固有思维模式.

PREVIOUSLY AT

BMW Group
Share

编写一次代码并在多个平台上使用它一直是许多软件开发人员的梦想. Although this has been possible for some time now, it always came at the cost of maintainability, ease of testing, or even worse, poor user experience.

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

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

But then, I came across Xamarin.

Cross-platform development with Xamarin

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

What Is Xamarin?

Xamarin is a development platform 它允许你为iOS编写跨平台的原生应用程序, Android, and Windows Phone in C# and .NET.

Xamarin为原生Android和iOS api提供c#绑定. 这让你能够使用所有Android和iOS的原生用户界面, notifications, graphics, animation, and other phone features—all using C#.

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

Xamarin’s port of .NET includes features such as data types, generics, garbage collection, language-integrated query (LINQ), asynchronous programming patterns, delegates, 和Windows通信基础(WCF)的子集. 库是用一个逗留符来管理的,它只包含被引用的组件.

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

The scope of Xamarin

Writing Cross-platform Applications

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

  • Portable Class Library (PCL)
  • Shared Project

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

Xamarin's connections and limitations

下表显示了哪些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, as explained in the previous chapter, 为一个平台编写的代码很容易在其他平台上重用.

Where does MvvmCross come into place, then?

MvvmCross, as the name may have hinted, 使得在Xamarin应用程序中使用MVVM模式成为可能. It comes with a bunch of libraries, APIs, 以及在跨平台应用程序开发中非常方便的实用程序.

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

Structure of an MvvmCross Solution

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

.Core
.UI.Droid
.UI.iOS

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

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

The business logic layer, data layer, 和后端通信都是Core项目的理想选择. 通过视图层次(活动、片段等)进行导航.) will be achieved in the Core.

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

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

How is this pattern used in MvvmCross?

Well, 因为我们想要实现代码的高可重用性, we want to have as much as we can in our Core, which is a PCL project. 因为视图是代码中唯一一个平台与另一个平台不同的部分, we can’t reuse them across platforms. 该部分在与平台相关的项目中实现.

MvvmCross structure

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

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

Creating an MvvmCross Core Project

Open Xamarin Studio and create a solution named ToptalExampleSolution:

Creating the solution

由于我们正在创建一个Core项目,因此坚持使用命名约定是一个好主意. Make sure the Core suffix is added to the project name.

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

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

In the search field, we can search for MvvmCross, 它将过滤出与MvvmCross相关的结果,如下所示:

Filtering results

Clicking on the Add Package button will add it to the project.

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

Let’s define our first ViewModel. 为了创建一个文件夹,创建如下层次结构的文件夹:

Recommended hierarchy of folders

Here is what each of the folders is about:

  • Models: Domain models which represent real state content
  • Services: 存放我们的服务(业务逻辑、数据库等)的文件夹.)
  • ViewModel: The way we communicate with our models

Our first ViewModel is called 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();
                });
            }
        }
    }

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

Android UI

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

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

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

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

Adding a reference to the PCL project

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

现在我们必须告诉MvvmCross它应该如何设置我们的应用程序. In order to do that, we have to create a Setup class:

namespace ToptalExample.UI.Droid
{
    public class Setup : MvxAndroidSetup
    {
        public Setup(Context context)
            : base(context)
        {
        }

        protected override IMvxApplication CreateApp()
        {
            return new Core.App();
        }
    }
}

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

    public class App : MvxApplication
    {
        public override void Initialize()
        {
            RegisterAppStart(new AppStart());
        }
    }

    公共类AppStart: MvxNavigatingObject, IMvxAppStart
    {
        public void Start(object hint = null)
        {
            ShowViewModel();
        }
    }

In the App class, we are creating an instance of AppStart, which is going to show our first ViewModel.

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



    
    
    
    

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

The View class can be an activity or a fragment. 为了简单起见,我们使用一个activity来加载给定的布局:

[Activity(Label = "ToptalExample.UI.Droid", Theme = "@style/Theme., MainLauncher = true, Icon = "@mipmap/ Icon ")]
    public class MainActivity : MvxAppCompatActivity
    {
        protected override void 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, just create an iOS project. 只要确保您保持命名约定的一致性即可.

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

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

public class Setup : MvxIosSetup
    {
        (MvxApplicationDelegate, appDelegate, IMvxIosViewPresenter, presenter)
            : base(appDelegate, presenter)
        {
        }

        protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp()
        {
            return new App();
        }
    }

Note that the Setup class now extends MvxIosSetup and, for Android, it was extending MvxAndroidSetup.

One addition here is that we have to change our AppDelegate class.

AppDelegate 在iOS上负责启动用户界面, 所以我们需要告诉视图将如何在iOS上呈现. You can learn more about presenters here.

[Register("AppDelegate")]
    AppDelegate: MvxApplicationDelegate
    {
        // class-level declarations

        public override UIWindow Window
        {
            get;
            set;
        }

        uiapplicationapplicationnsdictionary启动选项
        {
            Window = new UIWindow(UIScreen.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, we have to define our bindings in a different way, 通过代码(虽然我们也可以在Android上这样做), for some cases, it is required to do so).

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

But, how does MVVMCross know which view to load?

There isn’t any magic in that. 当我们为Android (Activity或Fragment)创建视图时,我们正在扩展一个带有类型参数的基类(MvxAppCompatActivity). When we call ShowViewMolel, MvvmCross looks up for a View which extends Activity or Fragment class with type parameters VM. 这就是为什么不允许为同一个ViewModel拥有两个视图类的原因.

Inversion of Control

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

无需运行时注入依赖项或编译时注入, it is not easy to create loosely coupled, reusable, testable, and easily maintainable components. The idea of IoC and DI has been known for a really long time; details about IoC can be found in many articles online. You can learn more about these patterns from Martin Fowler’s introductory article.

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

In order to get loosely coupled components, 我们永远不应该要求类的具体实现. 要求具体的实现限制了在运行时更改实现行为的能力(您不能用另一个实现替换它). It makes it difficult to test these components.

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

public interface IPasswordGeneratorService
{
     string Generate(int length);
}

And implementation:

    PasswordGeneratorService
    {
        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, which we are responsible for providing.

In order for MvvmCross to inject a PasswordGeneratorService 我们需要告诉MvvmCross要使用哪个实现. 如果我们想在两个平台上使用一个实现,我们可以在其中注册实现 App.cs, after application registration:

public override void Initialize()
        {
            RegisterAppStart(new AppStart());

            Mvx.LazyConstructAndRegisterSingleton();
        }

The above call to static method LazyConstructAndRegisterSingleton registers the implementation to be injected. 此方法注册适当的实现,但不创建对象.

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

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

在某些情况下,我们不希望应用程序中只有单例. 我们的对象可能不是线程安全的,或者可能有其他原因使我们希望总是有一个新实例. If that’s the case, MvvmCross provides method Mvx.RegisterType(), 哪一个可以用来注册实现,在需要的时候实例化一个新的实例.

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

ippasswordgeneratorservice: ippasswordgeneratorservice
    {
        public string Generate(int length)
        {
            return "DroidPasswordGenerator";
        }
    }

我们实现的注册是在 Setup.cs class under the Droid project:

InitializePlatformServices()
        {
            base.InitializePlatformServices();

            Mvx.LazyConstructAndRegisterSingleton();

        }

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

当我们注册多个单例实现时, MvvmCross将只使用最后注册的实现. All other registrations are going to be discarded.

Build Cross-platform Apps with Xamarin

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

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

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

Hire a Toptal expert on this topic.
Hire Now
Emran Bajrami

Emran Bajrami

Verified Expert in Engineering

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

Member since January 14, 2016

About the author

Emran is a results-driven, articulate, 以及善于分析的Android和Java工程师,能够跳出固有思维模式.

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

PREVIOUSLY AT

BMW Group

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Toptal Developers

Join the Toptal® community.