站点图标 江湖人士

我的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时的一些内容。

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)

 


补几张图结束本篇

         

退出移动版