老司機學新平台 - 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM