西邮图书馆(UWP)总结

时间:2021-08-10 10:48:19

     最近一直准备找实习,分享的技术很少了,西邮图书馆(UWP)算是我的比较好的项目了,但是里面还有很多不足之处,今天写这篇博客,一个是给自己一个总结,另一个,如果今后实验室有学习UWP相关技术的学弟学妹,希望能看完这篇博客,完善我在后面介绍关于这个项目的不足之处。其实关于WP的博客不想写了,每一次写都是一种难过,有的人可能体会不到吧,我的班长从7.5开始学习到8.1,带领我学习8.1,他去X度公司之后,我自学了UWP。可以说,看见别的组资料一大堆,各种开源,拿起WP,仅仅的两本书。这期间,多少心酸还是有的(相信大家都能明白,同样都是实验室的,在安卓,苹果,Web环境中生存的一个WP Coder的感受),今年考虑撤销我生活了两年多的Windows 组(至于为什么,实验室的都知道),开会宣布的时候,心里真的很难过。如果说对WP有什么话,那就是爱过,引用一句老师的话语,生活还要继续。但还是很希望学弟学妹们能完善这个应用,后面我有很多想法,一些不错的。
项目的应用地址:西邮图书馆UWP

     首先从项目背景开始讲吧,西邮图书馆整个项目,是西邮移动应用开发实验室和学校图书馆部门合作的一个项目。在很早没有学习数据库的时候,我们用Node.Js进行网络爬虫,写成统一的API提供给各个平台进行开发。当Web版出现的时候,引起了学校的注意,同时,图书馆老师邀请我们合作开发,Web,iOS,Android,Windows 10 D/M。我负责Windows 开发。因为当时比较紧,用了一个月,基本功能都完成了,但是对于整个项目,我还不是很满意。
    该UWP 项目采用 MVC 架构, Model-View-Controller 之间采用观察者模式编程,通过事件驱动来实现。Model层封装了各种Model实体类,并且继承了INotifyPropertyChanged接口,因为在后面的数据绑定中,很有可能对于一个对象,当里面数据发生改变时候,前台界面也需要做出相应的更新。Controller层封装了数据逻辑业务,网络请求中获取的数据,在Controller中,进行相应的JSON解析,并且反序列化为对应的Model对象。通过自定义事件,以及回调的方式传递给View层,View层只需要进行数据上下文绑定,以及获取用户响应操作传递给Controller,由Controller进行相应的逻辑业务操作。如下图
西邮图书馆(UWP)总结

下来说说里面的一些我认为比较有意思的技术吧。

    一、考虑用户的信息的时候,我采用的是全局单例模式。因为在整个应用程序中,无论是登录过还是没有登录过,只有一个用户,但是你可以在单例对象中设置一些用户的属性来保存数据。这里需要提醒一点就是,尽量考虑多线程单例,可以双重判空加锁来实现。还有一种方式是利用C#的语言特性,static readonly关键字实现。
    二、在新闻以及公告的列表方面,因为他们同主页是在同一个Page下,如下图
西邮图书馆(UWP)总结
西邮图书馆(UWP)总结
对于这种界面,有一个问题就是如果当前没有网络,或者加载失败时候,新闻和公告就是一张空白。对于这种尴尬的情况,可以考虑进行缓存,每当有新的数据添加进来时候,进行缓存,当下次如果出现加载失败或者网络断开时候,可以考虑从缓存中取出来。这里我用的是应用设置存储,它是一个全局单例。采用的算法,就是我们经常说的先进先出,FIFO。至于为什么不是其他算法呢,因为考虑到实际情况,这些新闻公告列表都是有时间次序的。
    三,关于上滑加载和下拉刷新,刚才大家都看到首页有新闻和公告列表。有了列表肯定要有上滑下载和下拉刷新。我想的是这里都是通过ScrollerViewer的实际滚动距离和可滑动距离作比较,如果大于等于可滚动距离,就证明到底了,我们可以进行分页网络请求数据。但是通过获取ListView里面的ScrollerViewer时候,这里有一个麻烦事情,怎么去获取ScrollerViewer。如果知道XAML特性的同学,都应该知道,它就是一棵文档树,那岂不是我们学过数据结构中有一种算法,叫做树的按层遍历,不断地进行入队出队,获取队首,判断是否为ScrollerViewer。这样就可以拿到它了。
代码如下:

 //如果学过数据结构的童鞋,应该知道类似于树的按层遍历
public T FindChildOfType<T>(DependencyObject root) where T : class
{
//创建一个队列来存储根节点的对象
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = VisualTreeHelper.GetChild(current, i);
//利用as关键字的特性
var typechild = child as T;
//如果是要我们找的那个节点,则返回
if (null != typechild)
{
return typechild;
}
queue.Enqueue(child);
}
}
return null;
}

(在后来,一次偶然的机会,看到ListView的ContainerContentChanging事件也可以实现上滑加载,至于这个事件是什么,有关虚拟化的很厉害的一个技术,可以下来研究)。下拉刷新那块,其实试了很多,自己写过,外层嵌套ScrollerViewer来实现,但是发现效果不是很好,用codeplex第三方开源库时候,也不是很好,我问学长,如果下拉刷新出现淘宝那种Bug,拉下来之后出现回不去现象,或者卡住,要不要这种功能,他说不要,可以写一个控件按钮实现。这个是后面要说的增添新功能,
    四、关于条形码扫描。这个是最头疼的东西。ZXing大家都知道,可是,发现不咋会写。网上搜出来几乎全是8.0和7.5的,至于RunTime的很少,没办法,自己实现这个实现了一个月(从开始的写这个程序一直到最后第一版本上线,总共一个月),出现这么几个问题,扫描时间太长,扫描率太低,每当拍照时候总是卡一下。针对扫描率低的问题,我是这样做的,每次拍照完,显示图片,利用控件的Clip属性进行截图,这样就可以保证获取到中间二维码或者条形码区域了,这样虽然识别率高了,但是时间太长了,体验不好。至今一直在找好的办法。
    五、登陆成功的回调函数里面获取历史信息,借阅信息,收藏信息,以及我的个人信息。这里采用异步请求,对于用户单例信息一定要加双重判空和加锁。每当获取一个信息时候,都要判断是否可以跳转到我的个人信息里面,但是这里有个问题,如何判断这四个信息完成时才跳转,而且还有多线程同时访问跳转页面的函数,所以尽量用一个flag变量,加锁,当flag等于4时候,就说明信息获取完成,这个时候在跳转界面。(这里存在Bug,解决方案有了,只是太忙了,没有更新应用,等忙完这段,加完后面所有功能时候,再更新)最后关于登录方面,有一个取消登录的功能,这个其实是没有办法取消的,异步请求很难取消,只要设置Flag变量来达到取消登录功能。
    六、关于第三方开源库的使用。 比如NewtonSoft.Json,ZXing,还有微博微信的SDK,某些开源库只针对8.0,或者Windows 8.1的,需要自己下载源代码,重新编译为可移植的SDK,才能使用。我的微博列表中有。

    最后,关于这个项目,我自己想的某些功能,希望以后有时间,或者学弟学妹们来完善吧。
    首先,关于图片下载与缓存,虽然这是一个很小的应用,但是尽量往全面的方向考虑。关于异步加载,可以封装对于每个图片对象的下载,用基于HttpClient的异步方法,对图片进行下载,INotifyPropertyChanged接口实现通知。

 HttpClient client = new HttpClient();
byte[] buffer = await client.GetByteArrayAsync(small);
// 注意需要把数据流重新复制一份,否则会出现跨线程错误
// 网络下载到的图片数据流,属于后台线程的对象,不能在UI上使用
streamForUI.Write(buffer, 0, buffer.Length);
streamForUI.Seek(0, SeekOrigin.Begin);
//触发UI更新界面
if (_page != null)
{
await _page.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
BitmapImage bm = new BitmapImage();
bm.SetSource(streamForUI.AsRandomAccessStream());
//把图像放到弱引用里面
if (null == SmallBitmapImage)
{
SmallBitmapImage = new WeakReference(bm);
}
else
{
SmallBitmapImage.Target = bm;
}
//触发UI绑定属性的改变
OnPropertyChanged("SmallImageSource");
})
;
}

自建缓存,但是这里牵扯到一个问题,如果图片过多怎么办,要考虑内存的话,我的做法是用弱引用,当内存不足时候,垃圾回收会回收弱引用。以上是我目前现在实现的。但是考虑到如果用户每次搜索的话,都要进行图片下载,这种体验是不好的,浪费流量,目前的想法是基于LRU算法,实现文件缓存,利用两个hash,一个key存储图书ISBN,value存储图片的路径。另一个实现LRU算法计数,通过每次更新进行相应的替换以及更新。
    其次,对于搜索记录,目前这块还没做,我的想法是,基于队列的数据结构,实现对用户搜索信息的记录,自定义队列大小,通过入队出队等相关操作达到更新数据。
    然后关于界面适配问题,我只做了简单的M平台界面,对于PC,因为时间的关系,么有继续在设计界面。由于这个应用比较简单,所以如果要做界面适配,不能简单只做RelativePanel和VisualStateManager这些来进行界面适配,我想的是对于D和M需要采用不同的设计来实现,因为之前采用中间分离业务逻辑,所以View层变化不太,只是需要写 XXXPage_Phone.Xaml 或者 XXXPage_DeskTop.Xaml即可。
    对于事件,虽然好用,但是不要滥用,比如,手机的物理返回键,因为每次不同界面返回键可以处理不同的事情,每次进入页面注册返回事件时候,一定要记得离开页面取消返回事件,这里很容易引起事件的广播。

  protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);

if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
}
    protected override void OnNavigatedFrom(NavigationEventArgs e)
{

base.OnNavigatedFrom(e);

if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
}
}

    最后集成小娜,搜索图书功能,这里没有什么技术难度,微软有相关API,可以在MSDN查看,也可以在看人家源代码。(因为之前写过8.1小娜控制电脑。所以不是很难)