在上一篇Xamarin开发环境及开发框架初探中,曾简单提到MvvmCross这个Xamarin下的开发框架。最近又评估了一些别的,发现老牌Mvvm框架Prism现在也支持Xamarin Forms了,可喜可贺!今天我们就来近距离尝试、比较一下,分别基于这两个框架写一个简单Android/iOS跨平台应用的感受。

Prism

Prism框架应该来源于微软的Microsoft patterns & practices的Prism Guidance。最初,应该被更多用于WPF开发。我对WPF了解太少,就不评论它的过去了,我们这里重点关注他目前Preview状态的Prism Xamarin Forms支持。它提供了一个VS2015的模版插件,可以从这里直接下载安装,也可以从VS2015的Extensions and Updates菜单里面下载安装。安装这个插件,并重启VS2015后,我们可以看到,项目模版里多了一个Prism分组,包含三个项目类型,其中一个是Prism Unity App (Forms),就是for Xamarin Forms的。名字里包含Unity,那是指这个模版使用Unity DI框架。

事实上Prism支持Autofac,NInject等各种DI框架,不过模版只有Unity的。我们来尝试新建一个项目。相比于Xamarin官方及其他开发框架,它的项目模版,提供了非常友好的UI,可以选择新建的项目需要支持哪些平台。如下图:

我们就选Android和iOS。新建的项目很自然的包含一个PCL格式的共享项目,和一个Droid项目,一个iOS项目:

从目录结构来看,和Xamarin Forms官方的模版生成的项目非常类似,表面看来,好像就是在共享项目里多了ViewModels和Views目录。我们来看看Droid的MainActivity类,我们知道这个类是每一个Xamarin Droid项目的入口类,基本相当于Console程序的Main方法的地位。可以看到,LoadApplication()方法还是接受一个App类的instance,(这个类还是定义在共享项目里的,我们下面再细说),不过,这个App类的构造函数接受一个实现了Prism的IPlatformInitializer接口的AndroidInitializer。它只包含一个RegisterTypes(UUnityContainer container)方法,非常容易理解,就是用来配置Unity容器的,我们可以在这里注册各种业务层、数据层等等的接口和类到Unity。

好像Droid项目相比官方Xamarin Forms模版也就这点区别了,可见Prism对官方模版架构的改动非常小。我们再来看看iOS项目的AppDelegate类。这个类和MainActivity的地位相当,也是iOS项目的入口类。看上去也是和MainActivity做的事情非常类似,这里也包含一个iOSInitializer类实现了IPlatformInitializer接口。太容易理解了,自描述,都不需要文档,我喜欢。

下面就剩下在共享项目了。我们先来看看App类,它还是包含一个App.xaml和一个code behind文件。App.xaml看上去和Xamarin Forms官方模版的不一样了,它继承于PrismApplication类。

App.xaml.cs的内容就有比较大的不同了。首先,他当然需要接受一个IPlatformInitializer,并传递给基类,这个容易理解。此外,两个override方法,一个RegisterTypes()用于注册功能页面。另一个OnInitialized()用于指定默认打开那个页面,并且还可以给页面传参数。还是足够简单的。

很明显,项目运行时,会先显示MainPage,而MainPage的xaml和对应的ViewModel分别定义在Views目录和ViewModels目录中。MainPage是一个标准的xaml page,唯一的和Xamarin Forms版本不同的是,它包含了一个prism:ViewModelLocator.AutowireViewModel="True"的属性,从字面意思,我们就能知道,它表示,他会自动resolve并绑定到一个同名ViewModel,也就是ViewModels目录中的MainPageViewModel类。而这个类的实现也是不能再简单了。主要就一个构造函数加两个方法,一个OnNavigatedFrom(),一个OnNavigatedTo(),真的不需要文档,就能理解,一个是页面load时执行的逻辑,一个是页面离开时执行的逻辑。

让我们来尝试增加一个第二个页面和对应的ViewModel,看看页面跳转怎么玩。我们来新建一个SecondPage.xaml和对应的SecondPageViewModel类。页面功能就照抄MainPage了,只是显示的text不同。然后,我们需要在App类的RegisterTypes()里注册SecondPage:

接下来,我们来给MainPage.xaml增加一个按钮,点击切换到SecondPage:

当然,MainPageViewModel里我们也要添加相应的代码,主要是,要依赖注入一个INavigationService,用来执行跳转,当然还要一个LoadSecondCommand绑定到前面那个按钮。

Ok, 运行程序,点击按钮,就能从MainPage跳转到SecondPage了。

Prism的简单示例就到这儿。总体的感觉就是,Prism框架和Xamarin Forms本身的架构结合地的非常自然,接口定义,和编码方式也非常符合一般人的思路,几乎不需文档就能理解。

MvvmCross

下面我们再来近距离看看MvvmCross的情况。MvvmCross也有一个VS2015的插件,提供了支持Forms的项目模版。我们来练一把。下载安装这个插件后。我们来新建一个项目。新建的项目是下面这个样子:

两个Test项目我们先不管。我们可以看到,它相比Prism多了不少东西,我们一个一个来看。这次,我们先从共享项目开始。Model和Repository目录可以先忽略,他们只是生成了数据访问的repository接口和Model,其实和MvvmCross本身没什么关系。Resources和Services目录也可以先放一边,因为,它们实现了一个简易的多语言支持方案,接口其实非常简单明了。ViewModels和Views目录,其实和Prism的是一个概念,不过略有差别。

第一个差别是,MainPage.xaml中定义了一个xmlns:res="clr-namespace:MvvmCrossForms1.Core.Resources;assembly=MvvmCrossForms1.Core"属性,做什么用的呢?通过它可以访问Resources目录中定义的多语言翻译的文本资源。有了它,我们可以很方便的绑定多语言文本资源到xaml上的属性。

第二个差别是,所有的ViewModel类需要继承自MvvmCross提供的MvxViewModel类。并且,需要为每个ViewModel属性的set实现,调用基类的RaisePropertyChanged()方法。这样,理论上,它就能动态获知View上的属性变更,为实现动态数据绑定,提供了可能。

接下来,是App类。虽然名字还是叫App,实际上,它这个App类并不是最终继承自Xamarin Forms的Application类的,它不是一个View,也没有xaml,而是继承于MvvmCross自己的Application类。在具体的Driod和iOS项目到时可以看到,它的框架内部实际上封装了真正的Xamarin Application类。这个App类只包含一个Initialize()方法实现,前半部分调用它的内部IoC容器的配置方法,将所有的Services和Repositories接口和类,注册到了IoC容器。中间设置了App的当前语言。最后一行,调用RegisterAppStart<ViewModels.MainViewModel>()显示默认的MainPage页面。但是注意,比较特别的是,它不是通过页面的名称,而是通过ViewModel类,强类型方式制定要显示哪个页面的。

好了,共享项目就这些特别了。我们来演示如何进行页面跳转。新建名叫SecondPage的xaml和一个名叫SecondViewModel的ViewModel类。然后,给MainPage.xaml添加一个Button如下:

然后,在MainViewModel类中,添加一个ShowSecondCommand就行了,这个比Prism的版本简单多了。注意,它这里也是通过强类型的ViewModel进行页面跳转的。强类型的好处是,如果有任何重构,所有的引用会自动更新,不像Prism的字符串,还要自己手动改。

再来看Droid项目。Droid项目的Bootstrap目录一般用于MvvmCross的各种插件的初始化,这里不详述。Services目录呢,一般用于放置共享项目中定义的某些功能接口的Droid特定实现,这里也可以先忽略。Linker类可以忽略,是它生成了给MvvmCross框架自己用的。

这个Droid项目真正的入口是SplashScreen,它基于标准的Xamarin Activity提供了一个App开机页面的默认实现。它的isInitializationComplete()方法,指定了,App完成后,显示MvxFormsApplicationActivity这个Activity。

SplashScreen的基类内部,会通过反射,去寻找项目中一个名叫Setup的类,这个Setup类里,就是放所有App初始化代码的地方。所有的初始化执行完之后,SplashScreen类的isInitializationComplete()方法才会被调用。

一旦isInitializationComplete()方法被调用,MvxFormsApplicationActivity类被执行,在它的OnCreate()方法会最终启动,定义在共享项目里的App类,然后MainPage才会被真正显示。

iOS项目的情况,略有不同,没看到类似Droid项目的SplashPage功能(可能是不同的team负责iOS和Droid部分开发的?貌似Droid版本功能更完善一些。这个还要在摸索下)。iOS项目的启动类还是AppDelegate,它的FinishedLaunching()方法简单的调用了Setup类,并且直接启动了共享代码里的App类,这样MainPage就能被显示了。

老实说,相比Prism的执行过程,MvvmCross的确实有点绕。不过,它帮你做了好几件任何App开发可能都需要处理的事,包括开机画面、初始化管理、简化的依赖注入、多语言支持,更简单的页面跳转,更简单的数据绑定,另外它还提供了很多其他的插件模块。最关键的是,在MvvmCross的架构下,那些真正复杂难懂的部分,其实都是框架级别的代码,而增加View,ViewModel,Service等这些业务相关的模块的管理,反而比Prism下更简单。所以,如果要我选的话,如果要开发的是一个企业App,功能繁多,需要支持多语言的App,我可能还是会倾向于MvvmCross。我也喜欢Prism的优雅,简洁。真心希望Prism未来能提供更多方便使用的插件,当然,也许只是我孤陋寡闻,果真如此的话就求之不得了!

BTW,不知道为什么,在我本机的iOS模拟器中,升级到iOS 10以后,MvvmCross的iOSApp运行都会闪退,感觉是MvvmCross的bug,希望能尽快有人fix。Prism的iOS项目运行没有任何问题。如果谁知道原因,也请不吝赐教,谢谢!

Update 1:

在MvvmCross的issue tracker新建了一个issue,描述了这个crash的问题:https://github.com/MvvmCross/MvvmCross/issues/1452

Update 2:

上面提到的iOS debugging MvvmCross app crash的问题已经解决了,我删了模拟器里所有以前部署的app,再次debug就可以运行了,很神奇。怀疑是不是,以前的某个app部署过程出错了,然后,后续部署的app和以前有问题的这个app冲突了。不过,至少咱有活路了不是?

Update 3:

免费附赠两行脚本,当Xamarin iOS app调试出错时,如何查看iOS模拟器的device log呢?只需要在MacOS里起一个terminal,执行下面的两条命令:

# list all device's device codes
instruments -s devices # view latest log of a specific device
tail -f ~/Library/Logs/CoreSimulator/<DEVICE_CODE>/system.log

老司机学新平台 - Xamarin Forms开发框架二探 (Prism vs MvvmCross)的更多相关文章

  1. 老司机学新平台 - Xamarin Forms开发框架之MvvmCross插件精选

    在前两篇老司机学Xamarin系列中,简单介绍了Xamarin开发环境的搭建以及Prism和MvvmCross这两个开发框架.不同的框架,往往不仅仅使用不同的架构风格,同时社区活跃度不同,各种功能模块 ...

  2. 老司机学新平台 - Xamarin开发之我的第一个MvvmCross跨平台插件:SimpleAudioPlayer

    大家好,老司机学Xamarin系列又来啦!上一篇MvvmCross插件精选文末提到,Xamarin平台下,一直没找到一个可用的跨平台AudioPlayer插件.那就自力更生,让我们就自己来写一个吧! ...

  3. 老司机学新平台 - Xamarin开发环境及开发框架初探

    随着被微软收购,最近一年间,Xamarin的火爆程度与日俱增.免费.更好的VS2015集成.更好的模拟器,甚至,在windows上运行和调试iOS平台程序,让我这样接触了十几年.NET平台的老司机,即 ...

  4. 老司机学Xamarin系列总目录

    Xamarin开发环境及开发框架初探 Xamarin Forms开发框架二探 (Prism vs MvvmCross) Xamarin Forms开发框架之MvvmCross插件精选 Xamarin开 ...

  5. C语言老司机学Python (五)

    今天看的是标准库概览. 操作系统接口: 用os模块实现. 针对文件和目录管理,还有个shutil模块可以用. 例句: import os os.getcwd() # 返回当前的工作目录 os.chdi ...

  6. C语言老司机学Python (一)

    Python 版本:3.6.4 参考网上教程:http://www.runoob.com/python3/python3-basic-syntax.html 开始了啊. 干咱们这行的老规矩,学新语言的 ...

  7. C语言老司机学Python (六)- 多线程

    前面的1-5都是比较基础的东西,能做的事情也有限. 从本节起,随着更多进阶技术的掌握,渐渐就可以用Python开始浪了. Python3使用threading模块来实现线程操作. 根据在其他语言处学来 ...

  8. C语言老司机学Python (三)

    条件语句: 注意1) condition后面的冒号 2) elif if condition_1: statement_block_1elif condition_2: statement_block ...

  9. C语言老司机学Python (二)

    标准数据类型: 共6种:Number(数字),String(字符串),List(列表),Tuple(元组),Sets(集合),Dictionary(字典) 本次学习主要是和数据类型混个脸熟,知道每样东 ...

随机推荐

  1. PHP 汉字拼音互转

    <?PHP function Pinyin($_String, $_Code='gb2312'){ $_DataKey = "a|ai|an|ang|ao|ba|bai|ban|ban ...

  2. Redis为什么使用单进程单线程方式也这么快

    [转] http://www.syyong.com/db/Redis-why-the-use-of-single-process-and-single-threaded-way-so-fast.htm ...

  3. 【转】CentOS上部署PPTP和L2TP over IPSec简要笔记

    PPTP部署 安装 PPTP 需要 MPPE 和较高版本的 ppp ( > 2.4.3 ) 支持,不过 CentOS 5.0/RHEL 5 的 2.6.18 内核已经集成了 MPPE 和高版本的 ...

  4. pyqt 发射接收信号

    翻页控件: 一.定义并发射信号: #!/usr/bin/python # #coding=utf-8 # __author__='' from PyQt4.QtCore import * from P ...

  5. 【系统篇】从int 3探索Windows应用程序调试原理

    探索调试器下断点的原理 在Windows上做开发的程序猿们都知道,x86架构处理器有一条特殊的指令——int 3,也就是机器码0xCC,用于调试所用,当程序执行到int 3的时候会中断到调试器,如果程 ...

  6. mac安装虚拟机

    1. 安装VirtualBox 2. 新建,按照步骤一步步选择 3.安装系统镜像 xp_sp3_74070.iso CN_WIN7_SP1_X64_33in1_V1.2.iso 设置磁盘分区等 4.V ...

  7. 一个简单的路由,用javascript实现

    前端路由在很多开源的js类库框架中都得到支持,如angularJS,Backbone,Reactjs等等.前端路由和后端路由原理一样,是让所有的交互和展现在一个页面运行以达到减少服务器请求,提高客户体 ...

  8. css中单位px、pt、em和rem的区别

    国内的设计师大都喜欢用px,而国外的网站大都喜欢用em和rem,那么三者有什么区别,又各自有什么优劣呢? px :像素(Pixel).相对长度单位.像素px是相对于显示器屏幕分辨率而言的.(引自CSS ...

  9. 关于兼容IE的一些策略

    --css 盒子模型下的 box-sizing 属性,只兼容到ie8: -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o- ...

  10. Java概念性问题

    一.变量命名的五个要素 由字母.数字.“_”和“$” 组成 首字母不能为数字 大小写敏感 不能使用Java的保留字和关键字 可以使用中文命名,但是不建议 二.java的基本数据类型 整数类型:byte ...