老司机学新平台 - Xamarin Forms开发框架二探 (Prism vs MvvmCross)
在上一篇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)的更多相关文章
- 老司机学新平台 - Xamarin Forms开发框架之MvvmCross插件精选
在前两篇老司机学Xamarin系列中,简单介绍了Xamarin开发环境的搭建以及Prism和MvvmCross这两个开发框架.不同的框架,往往不仅仅使用不同的架构风格,同时社区活跃度不同,各种功能模块 ...
- 老司机学新平台 - Xamarin开发之我的第一个MvvmCross跨平台插件:SimpleAudioPlayer
大家好,老司机学Xamarin系列又来啦!上一篇MvvmCross插件精选文末提到,Xamarin平台下,一直没找到一个可用的跨平台AudioPlayer插件.那就自力更生,让我们就自己来写一个吧! ...
- 老司机学新平台 - Xamarin开发环境及开发框架初探
随着被微软收购,最近一年间,Xamarin的火爆程度与日俱增.免费.更好的VS2015集成.更好的模拟器,甚至,在windows上运行和调试iOS平台程序,让我这样接触了十几年.NET平台的老司机,即 ...
- 老司机学Xamarin系列总目录
Xamarin开发环境及开发框架初探 Xamarin Forms开发框架二探 (Prism vs MvvmCross) Xamarin Forms开发框架之MvvmCross插件精选 Xamarin开 ...
- C语言老司机学Python (五)
今天看的是标准库概览. 操作系统接口: 用os模块实现. 针对文件和目录管理,还有个shutil模块可以用. 例句: import os os.getcwd() # 返回当前的工作目录 os.chdi ...
- C语言老司机学Python (一)
Python 版本:3.6.4 参考网上教程:http://www.runoob.com/python3/python3-basic-syntax.html 开始了啊. 干咱们这行的老规矩,学新语言的 ...
- C语言老司机学Python (六)- 多线程
前面的1-5都是比较基础的东西,能做的事情也有限. 从本节起,随着更多进阶技术的掌握,渐渐就可以用Python开始浪了. Python3使用threading模块来实现线程操作. 根据在其他语言处学来 ...
- C语言老司机学Python (三)
条件语句: 注意1) condition后面的冒号 2) elif if condition_1: statement_block_1elif condition_2: statement_block ...
- C语言老司机学Python (二)
标准数据类型: 共6种:Number(数字),String(字符串),List(列表),Tuple(元组),Sets(集合),Dictionary(字典) 本次学习主要是和数据类型混个脸熟,知道每样东 ...
随机推荐
- html内容超出了div的宽度如何换行让内容自动换行
在显示评论列表的时候因为有固定宽,但是显示的内容超出的了div的宽,在这种情况下我们需要将其换行,实现的css代码如下 在工作中评论内容测试遇到评论着的评论内容为:"dddddddddd ...
- Java 之 I/O流
1.流 a.分类:①字节流:InputStream.OutputStream ②字符流:Reader.Writer b.选择:①判断是 输入 还是 输出 (站在程序的立场上) ②判断是 字节 还是 字 ...
- Android 谈谈封装那些事 --BaseActivity 和 BaseFragment(二)
1.前言 昨天谈了BaseActivity的封装,Android谈谈封装那些事--BaseActivity和BaseFragment(一)有很多小伙伴提了很多建议,比如: 通用标题栏可以自定义Vi ...
- mac下安装mysql教程
由于更换了mac电脑,需要装一个mysql,经过各种资料的翻阅,各种踩坑,终于装完了,记录一下,方便大家参照: 1.下载最新的mysql安装包,下载地址:http://dev.mysql.com/do ...
- Solr DIH JDBC 源码解析
Solr DIH 源码解析 DataImportHandler.handleRequestBody()中的importer.runCmd(requestParams, sw) if (DataImpo ...
- js排序算法总结—冒泡,快速,选择,插入,希尔,归并
相信排序是任何一个程序猿都会用到的东西,今天简单总结记录下常见的排序算法. 一.冒泡排序 说起冒泡排序,可能每个人都不会陌生,实现思路相当简单明了,就是不停的对数组进行两两比较,将较大(较小)的一项放 ...
- 做 Web 开发少不了这些的
抱歉,似乎有些标题党了.最近做服务器的热备,整理了些李纳斯工具的适用方法.看看还有不错的. 基本命令 sleep 500 暂停 ctrl + z 暂停 progress & 后台运行 jobs ...
- bzoj1051Tarjan裸题
tarjan缩点+判断出度为0的点 所以不需要新建边 #include <cstdio> ,time=,T=,sum=,ans=; ],to[],nex[],fir[],dfn[],low ...
- CEF中select选项错位的解决方法
使用cef加载页面,移动窗口后选项的位置并不会变化,仍保持上次打开的位置. 经过google查找到这是一个已经解决了的问题:https://bitbucket.org/chromiumembedded ...
- [RxJava^Android]项目经验分享 --- 异常方法处理
简单介绍一下背景,最近RxJava很火,我也看来学习一下,计划在项目的独立模块中使用它.使用过程中遇到很多问题,在这里记录分享一下.可能有使用不当的地方,大家多多包涵.对于RxJava的基本概念和功能 ...