上一篇交代了我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)
补几张图结束本篇