[Xamarin]我的Xamarin填坑之旅(二)
上一篇交代了我Xamarin填坑的背景,大概聊了聊第一步環境配置,第二步創建項目和開發框架選擇。如果有一個可用的梯子,這部分基本不會出錯。
接下來就具體聊一聊寫代碼的過程中遇到的一些事兒。
第三步是碼代碼:
①Http相關:
我做的項目是一個校園助手,目前提供的功能絕大多數是查詢功能。或者說,就是簡單的爬蟲,從校園服務器上爬取相關網頁。因此,結合校園網站以及我的自身需求,我寫了一個簡單的用于發送Http請求的服務類HttpService,封裝了一些Request方法:
public async Task<string> SendRequst(string uri, HttpMethod method, IDictionary<string, string> dic = null, string referUri = "", CancellationToken cancellation = new CancellationToken()) { HttpResponseMessage response = null; Encoding encoding = Encoding.UTF8; try { if (!string.IsNullOrEmpty(referUri)) { _client.DefaultRequestHeaders.Referrer = new Uri(referUri); } if (method == HttpMethod.Get) { response = await _client.GetAsync(uri, cancellation); } else { FormUrlEncodedContent content = new FormUrlEncodedContent(dic); response = await _client.PostAsync(uri, content, cancellation); } var mediaTypeHeaderValue = response.Content.Headers.ContentType; if (mediaTypeHeaderValue != null && mediaTypeHeaderValue.CharSet != null) { if (mediaTypeHeaderValue.CharSet.Contains("gb2312")) { encoding = Encoding.GetEncoding("gb2312"); } } using (var stream = await response.Content.ReadAsStreamAsync()) { byte[] buffer = new byte[stream.Length]; await stream.ReadAsync(buffer, 0, buffer.Length); var str = encoding.GetString(buffer,0,buffer.Length); return str; } } catch { throw; } finally { response?.Dispose(); } }
這段代碼本身沒有問題,但是在Xamarin中有個坑。由于需要用到gb2312編碼方式,但是我在調試安卓項目的時候,卻遇到類似“not support 936 code page”錯誤,解決的辦法就是在安卓項目屬性中添加CJK編碼方式支持。

這兒有一個最佳實踐,分享自周岳老師:
一般所有的Http請求通過HttpClient發起,每一個HttpClient的對象都幫我們維護了Http請求的一些基本信息,包括本地緩存,Cookie等。那默認的,如果Xamarin中直接使用HttpClient,它的實現完全是.net的實現方式。在這兒,可以通過ModernHttpClient來完成一些優化。
在創建HttpClient的時候,可以傳入一個ModernHttpClient.NativeMessageHandler實例,作為默認的Handler。這樣在Android平臺會使用OKHttp來執行一些Http請求,細節可以看作者的Github
HttpClient client=new HttpClient(new ModernHttpClient.NativeMessageHandler() );
Nuget:ModernHttpClient
GitHub:ModernHttpClient
在Http請求這塊兒,除了上面這個最佳實踐,其他部分可以100%重用我UWP項目中的代碼。基本無需任何修改。
②HTML Parse:
因為校內網站很少有直接提供REST API服務,所以必須自己從各個頁面解析數據。對于Dom的分析,我選擇使用AngleSharp這個工具。這個工具現在已經可以支持Xamarin了。
值得高興的是,個別站點使用了JSON傳送數據,針對JSON的解析,目前.Net平臺最權威的就是Newtonsoft.Json這個庫了,配合Newtonsoft.Json以及.Net的dynamic動態類型,很容易能完成Json的解析。Xamarin完全支持!所以這部分的代碼也是100%重用。
官網:AngleSharp
③XXXService:
在上面介紹的Http相關請求中,為了使用方便,我封裝了一個基本的HttpBaseService類,主要就是這層各種XXXService獲取數據。校園助手目前是以查詢為主,包括查詢成績,課表等教務相關的信息,查詢一卡通余額,消費,解/掛失等簡單的個人信息。于是針對不同的功能大類別,我封裝了諸如EduService,InfoService等類,在類中實現了一些方法,用來完成獲取數據,解析數據的功能。對這些方法的調用,都是由ViewModel層來完成,所以這部分代碼和UWP項目中的也是所差不多。
④ViewModel:
根據MVVM的特點,原則上是一個View對應一個ViewModel。ViewModel層和我UWP項目中的一樣,變動不多。但是,在UWP項目中,我使用的是MVVMLight這個框架,而在Xamarin中選擇了微軟自家的Prism,所以在消息通知,頁面導航等方面會有一些不同。下面列舉一些使用Prism MVVM時的一些內容。
- 關于頁面導航:Prism在頁面導航方面,提供了一個接口Prism.Navigation.INavigationService,采用構造函數注入的方式。
- 關于加載數據的問題:每當創建一個ViewModel,我們希望去獲取一些數據,可能是存儲在本地的,也可能要從網絡上獲取的。但是考慮到性能問題,這部分數據不能放在構造函數里面。
1.比較好的辦法就是當導航到ViewModel對應的View的時候,再去加載數據,Prism框架提供了這樣一個接口來實現相關的功能
public interface INavigationAware { void OnNavigatedFrom(NavigationParameters parameters);//從當前頁面離開時 void OnNavigatedTo(NavigationParameters parameters);//導航到當前頁面時 }
可以通過實現該接口,然后在OnNavigationTo方法中去加載一些數據。當然這個接口的作用不限于此,主要作用還是處理頁面導航時傳遞的參數。
2.還有一個我在UWP中常用的辦法,就是自定義一個OnLoad方法,然后綁定到View的Loaded事件上面。但這兒有一個很尷尬的問題,Xamarin的Page中并沒有一個Loaded事件,相對變通的是它有一個Appearing事件,可以當作Loaded來用。具體的綁定方法如下:
public partial class XXXPage : ContentPage { public XXXPage () { InitializeComponent(); this.Appearing += XXXPage _Appearing; } private void XXXPage _Appearing(object sender, System.EventArgs e) { LoadedCommand?.Execute(null); } public static readonly BindableProperty LoadedCommandProperty = BindableProperty.Create("LoadedCommand", typeof(ICommand), typeof(CampusCardPage), defaultBindingMode: BindingMode.OneWay);//用于綁定的依賴屬性 public ICommand LoadedCommand { get { return (ICommand)GetValue(LoadedCommandProperty); } set { SetValue(LoadedCommandProperty, value); } } }
上述代碼大致在Page中實現一個自定義的LoadedCommand,然后在頁面觸發Apearing事件的時候,調用該命令的Excute方法,命令則通過數據綁定進行賦值,與ViewModel中的LoadedCommand相相綁定,這也是為什么把LoadedCommand定義為依賴屬性的原因。
通過上面提到的1,2兩點,就可以在導航到頁面以后加載一些數據了,如果配合.net的async/await異步編程模型,就能更加流暢的實現數據加載了。
④Views:
針對View的編寫是這次踏坑Xamarin最耗時的部分。雖然Xamarin.Forms可以用Xaml來編寫頁面,但是和UWP的XAML比起來,功能上差不多,體驗上卻很不好,尤其是自動補全和智能感知等方面。所以寫代碼會寫的很累。這些其實還好解決,畢竟熟悉一段時間后就基本沒有障礙了。唯一欠缺的就是針對XAML代碼的Previewer了,雖然今年的Connect()2016大會上,Xamarin Studio里面已經集成了初步的Previewer,但目前離正式發布還有一段距離,尤其是在VS里面。針對這點,給出一個稍微便捷的應對辦法。使用工具Gorilla-Player,在真機上預覽。具體使用可以看他的WIKI,下面展示一個使用該工具的截圖

當然目前這個工具問題還挺多,比如不支持靜態資源的引用等。在吐槽的同時,還是需要靜靜的等待官方的previewer正式發布。
針對View也有一個最佳實踐:
因為現在XAML編輯器還不能很好的提供智能感知和自動補全等功能,所以在自己寫一些屬性的時候,很容易出現拼寫錯誤的問題。往往這種錯誤在編譯的時候,并不會被編譯器檢查到,于是錯誤就會發生在運行時,往往耗時耗力。Xamarin為我們提供了一個特性,叫做XamlCompilationAttribute,用來提供編譯程序集時候的XAML拼寫檢查等任務。他可以應用到整個程序集,也可以之應用在單個的View上。只需要指定其參數為XamlCompilationOptions.Compile即可。
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]//用于整個程序集 [XamlCompilation(XamlCompilationOptions.Compile)]//只用于單個Page/View當然XamlCompilation怎么可能會只提供這么一點特性呢?它可以提升整個XAML的性能,通過預編譯為IL代碼的方式,減少加載XAML的時間。具體看官方文檔:
XamlCompilation(https://developer.xamarin.com/guides/xamarin-forms/xaml/xamlc/)
⑤數據存儲:
一個完整的App,必須有本地存儲,無論是緩存一些文件,還是存儲一些必要配置信息。Xamarin跨平臺提供了統一的數據存儲方式,但是在不同的平臺上,具體實現是不同的。數據存儲方面,我選擇了SQLite這個輕量級的數據庫,這也是移動開發本地存儲最適合的數據庫之一了。好在現在也有比較成熟的ORM工具來支持SQLite了。
Xamarin官方實例(https://developer.xamarin.com/guides/xamarin-forms/working-with/databases/)
nuget:sqlite-net-pcl(https://www.nuget.org/packages/sqlite-net-pcl)
補幾張圖結束本篇

在碼代碼的過程中,還遇到過一些其他的坑,以后慢慢更,因為這是連載系列......

浙公網安備 33010602011771號