提出厦门大学iOS移动图书馆客户端的构建策略,包括技术路线选定、需求调研、功能模块确定、UI设计、APP开发、软件测试等,对API开发、XML解析、多线程的实现和MVC软件架构模式应用进行描述。
This paper proposes Xiamen University iOS mobile library APP building strategies, including the selected technical type, user needs researching, function modules setup, UI designing, APP programming, and APP testing. It also describes API development, XML parsing, multi-threaded implementation, and MVC programming mode application.
随着智能手机等移动终端的盛行,移动图书馆已经成为图书馆的基础业务和必要服务。对高校图书馆而言,面向大学生这一大多拥有移动终端设备的年轻读者群,开发专门的移动图书馆APP是满足其移动信息需求的有效服务方式。业界已开发出一批出色的基于iOS的移动图书馆客户端,如中国国家图书馆[ 1]、上海图书馆[ 2]、汇文掌上图书馆[ 3]。厦门大学图书馆在充分调研读者需求和相关技术的基础上,于2011年9月适时推出了基于iOS的移动图书馆客户端。
iOS[ 4]由苹果公司开发,最初是为iPhone而设计,后来陆续应用于iPod Touch、iPad及Apple TV等产品线,目
前已成为最受应用开发者欢迎的操作系统平台。iOS开发只能采用基于苹果操作系统MAC OS X的XCode[ 5]开发套件作为开发环境。XCode开发套件包含XCode代码编辑调试器、Interface Builder界面编辑器以及iOS环境模拟器等几大组件,为iOS开发提供了完整的支持。从iOS 2.0开始,通过审核的第三方应用程序(APP)可通过苹果的APP Store进行发布和下载。APP Store现已成为一揽子服务的组合,即下载分发服务、软件代支付服务、内支付服务、推广平台、自动升级服务、Game Center、iCloud等的一个集合。
iOS APP客户端开发有三种模式:Native(原生)模式、Web模式和Hybrid(混合)模式[ 6]。其主要特性如表1所示:
![]() | 表1 iOS APP客户端三种开发模式的特性对比 |
在选择技术路线前最好考虑产品的未来发展与交互形式。厦门大学图书馆移动客户端采用Hybrid模式,既可充分利用基于iOS的各种接口,又能通过嵌入UIWebView的架构快速发布更新,方便迅捷地实现移动图书馆的功能。
为了确定移动图书馆客户端的功能模块,厦门大学图书馆针对学生读者群进行了一次关于移动图书馆的问卷调查——主要关注读者需要什么样的移动服务。调查报告显示,读者的需求主要集中在以下方面:图书馆通知;书评;书目推荐;图书馆借阅热门排行;电子书下载及在线阅读;图书检索;我的图书馆借阅情况;预定自习座位、查询图书馆空位;馆际互借。
基于此次调查,厦门大学图书馆确定了移动客户端的基本功能,即“发布信息,提供服务,揭示资源”,最终提供的移动服务包括在借图书查询、预约到书查询、图书搜索、预约、在线咨询等。
相对于PC操作环境下的鼠标移动点击的精确程度,手指在移动设备上进行触控操作的“模糊度”较大。鼠标指针在屏幕上移动时用户可以精确地知道它现在位于哪一点,但用手指触控时,用户一般只能点中某处“范围”。在这样的情境下,一个包含5个选项的子菜单大约要占据iPhone三分之一到二分之一的屏幕高度。因此,UI设计时必须对系统用到的事件有所取舍,要优先考虑最常用的操作,将不常用的操作另外处理。
对于页面的切换控制,iOS SDK(Software Development Kit)[ 8]提供了两种方式:UITabBarController和UINavigationController。UITabBarController提供的UITa
bBar的样式如图1所示:
经验表明,UITabBar里放置5个图标时最适宜用户点击,过多将导致用户的误操作。目前厦门大学图书馆移动客户端提供的功能已经超过5个,以后很可能陆续增加,因此更适合采用矩阵列表 + NavigationController的方式。矩阵列表可以在单个页面上放置多个图标,如果图标超过一个页面所能够容纳的数量,还可以采用UIPageControl配合来实现左右翻页的效果,如图2所示:
矩阵列表采用动态设计,程序在载入过程中会访问网络以获取最新的功能列表及相应的图标,然后再载入资源进行列表页面的UI呈现。
桌面应用的开发已经逐渐由C/S架构向B/S架构转变,此改变是基于用户体验提升的考虑。而APP的开发则正好相反,主要是采用类似C/S的架构,即将主要计算工作交由服务器端来完成,客户端只处理与用户的交互及数据的呈现。针对目前移动终端性能及网络通信速度的局限,采用这种开发方式可将计算量集中于服务器端,从而提高程序的运行速度,同时避免大量数据通信导致的反馈迟滞问题。
APP开发分为服务器端开发(即API开发)与客户端程序开发两部分。
APP完工后要进行测试。目前iOS系统已经发展到5.1.1版本,各版本的功能略有不同,5.0之后的系统还引入了通知中心等新特性,因此开发及测试过程主要的问题集中在对于各个版本的兼容测试上。
相对于Android系统而言,iOS的一大特点是其硬件设备统一。由于Android系统的开放性,其设备在软件、硬件上可说是千差万别,具体的差别包括屏幕分辨率、CPU主频、RAM大小以及系统版本等。而iOS在硬件上目前只包括iPhone、iPod Touch、iPad三种设备。对于各代设备的配置差异,XCode已经在底层做了兼容处理,无需开发者进行特别处理;对于采用Retina的设备(如iTouch4、iPhone4等产品),附加特定版本的图片即可解决。如果APP要兼容硬件差别最大的iPad,采用Universal模板就能解决程序在两类设备上的通用性。
iOS测试主要分为模拟器测试和真机测试两个步骤。XCode提供了所有版本的iOS系统模拟器,除了重力感应、摄像头、实际通话、发送短信息等由于客观条件无法实现外,模拟器的其他功能与真机并没有不同。如果程序中存在采用重力感应、摄像头的部分,可留到真机进行测试,或者采用变通的方法。如厦门大学图书馆移动客户端中有一个功能是直接用摄像头扫描、识别书籍条码,当在模拟器中用到这个功能时,可通过选择现成图片来替代直接拍照的方法来实现。
模拟器无法完成APP部分功能的测试,涉及到网络通信的功能更需要进行精确的调整,因此APP最后的测试必须在真机上进行。运营商的网络质量因时因地会有差别,APP应该针对各种或良好、或恶劣的网络环境进行充分测试,并针对不同的网络环境进行相应的调整,以期获得更好的用户体验。如对于图片的获取可在网速理想的情况下直接下载高清晰度图片,而在网络条件不许可时只下载低清晰度照片。
将尽可能多的任务交由服务器端处理,可显著加快运算的速度,并减少程序运行过程中的网络数据交互。实现方式是通过开发通用的应用程序接口(Application Program Interface,API),在客户端只需简单调用就能够实现数据交互,将繁琐的数据库操作语句转化成简单的“发送HTTP POST或GET请求”,而后“对返回的XML进行解析”的过程。另外,通用的API又能使多平台的客户端开发一举两得,因为基于API的操作对各平台而言都是通用的。
API的功能应涉及图书馆读者服务相关方面及周边功能。笔者将移动客户端API分为两种:服务型和资源型——前者如登录、在借情况、个人资料、讲座信息、预约情况等,后者如馆藏搜索、书目信息等,如图3所示:
iOS开发针对XML的解析有多种选择,iOS SDK提供了NSXMLParser和libxml2两个类库,另外还有很多第三方类库可选,例如TBXML、TouchXML、KissXML、TinyXML和GDataXML。这些类库的解析方式分为两种:DOM[ 9]和 SAX[ 10]。DOM解析XML时,读入整个XML文档并构建一个驻留内存的树结构(节点树),通过遍历树结构可以检索任意XML节点,读取它的属性和值。而且通常情况下,可以借助XPath,直接查询XML节点。SAX解析XML,是基于事件通知的模式,边读取XML文档边处理,不必等整个文档加载完之后再采取操作,当在读取解析过程中遇到需要处理的对象,会发出通知对其进行处理。SAX相对于DOM方式的优点在于无需等待载入整个XML文档再进行解析,对于大容量的数据交互有更好的效率优势。考虑到笔者的程序数据交互量较小,因此利用Google的GDataXML类库,在下载完所有数据后以DOM的获取方式解析。具体解析过程如下:
NSString *urlStr = [[NSString alloc]
initWithFormat:@"http://xx.xx.xx.xx/libsyswebservices/books.asmx/GetBorrowedBooks?redrCertId=%@",pRedrCertId];
//获取API(在借图书)
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:urlStr]] delegate:self];
GDataXMLDocument *xmlDocument = [[GDataXMLDocument alloc] initWithXMLString: options:0 error:&error];
GDataXMLElement *rootNode = [xmlDocument rootElement];
NSError *error2 = [[NSError alloc]init];
NSArray *mTitleList =
[rootNode nodesForXPath:@"//ArrayOfBorrowedBook/BorrowedBook/MTitle" error:&error2];
NSArray *mAuthorList =
[rootNode nodesForXPath:@"//ArrayOfBorrowedBook/BorrowedBook/MAuthor" error:&error2];
NSArray *callNoList =
[rootNode nodesForXPath:@"//ArrayOfBorrowedBook/BorrowedBook/CallNo" error:&error2];
for (int i=0; i<[propNoList count]; i++) {
//Book *book = [[Book alloc] init];
BorrowedBook *book = [[BorrowedBook alloc]init];
GDataXMLNode *node = [propNoList objectAtIndex:i];
book.propNo = [node stringValue];
node=[mTitleList objectAtIndex:i];
book.mTitle=[node stringValue];
node=[mAuthorList objectAtIndex:i];
book.mAuthor=[node stringValue];
node=[callNoList objectAtIndex:i];
book.callNo=[node stringValue];
[booksArray addObject:book];
[book release];
}
[error2 release];
return booksArray;
}
这是一个通过NSURLConnection建立HTTP连接,而后调用API获取返回XML文档的过程,文档最终解析成GDataXMLDocument,利用XPath配合路径获取其中的节点并得到节点值,将这些值存入BorrowedBook对象内即得到最终结果。
为便于说明,以上XML获取及解析过程采用了同步连接。要注意的是,在实际开发时极少使用同步连接来获取数据,而应将获取数据的工作交由主线程之外的线程来完成。这是因为在APP使用过程中,用户每次网络请求的发送及获取数据反馈所需时间都是开发者无法忽略的,如果程序并未针对这些请求进行相应处理,将费时的网络请求用单线程(主线程)来实现,那么用户点击某个发送请求的按钮后整个UI将锁住,一直到请求结束,在这段时间内用户无法对UI进行任何操作。更甚者,如果网络环境不佳、请求时间过长的话还会导致程序卡死。因此,多线程的使用是不可避免的。
iOS SDK中有多种实现多线程的方式,最便利的方式是使用NSURLConnection。网络数据请求发送之后,不管成功与否都应在UI将反馈信息呈现给用户。但由于数据请求并不是在主线程实现,而在iOS开发中非主线程无法进行UI操作——如何将非主线程中的反馈信息传递到主线程?如何将数据操作封装于底层,便于在程序各处直接调用?这两个问题的解决需要与iOS开发中的MVC(Model-View-Controller)架构模式结合起来。
如图4所示,在iOS的MVC模式中,与UI相关的程序(将数据呈现给用户以及接受用户的反馈)都由View层处理。View层的对象本身并不处理用户的请求与反馈,对象中的控件通过Outlet与Controller层中的对象一一联系;Outlet可以看成是指向View层中的指针,Controller层通过Outlet来完成与View层的交流。结合实例,Controller通过Outlet可以获取UI中某个控件的值如某本书的借期,也可以通过修改Outlet来修改借期。对于读者点击“续借”的按钮,由Controller层来响应其请求,其中可能包含判断能够续借的逻辑以及续借的操作。UI中呈现的数据,如借书列表页则由Controller管理维护。Model层是整个程序的基础。当Model层发生变化时通过Notification或KVO(Key-Value Observing)来告知Controller层,Controller层就能及时获取通知,并控制Outlet修改View中的控件,进而将UI呈现给用户[ 11]。
基于MVC的开发思路,笔者将数据交互相关的操作全部封装到Model层中,对于关键的多线程则结合MVC的处理方式以及向主线程反馈信息的传递方式。
实际操作流程如图5所示:
(1)用户点击页面中的“续借”按钮,View层响应点击,触发“Touch Up Inside”的action;
(2)该action由Controller层reBorrowedButtonPressed的方法接收:
- (IBAction)reBorrowedButtonPressed : (id)sender {
DAL *dal=[[DAL alloc]init];
[dal setDelegate:self]; //后续处理委托由本对象处理
NSString *username=[UserHelpler getUserName];
[dal reBorrow:username propNo:propNoLabel.text]; //发送数据请求
[dal release]; }
(3)Controller层通过Outlet控制View层中activityIndicator控件显示动画提醒用户操作正在进行;
(4)Controller层进行相应逻辑处理,并与Model层进行数据交互。
(5)Model层接收到Controller层请求后建立异步网络连接,从而实现多线程的网络数据请求:
-(void)reBorrow:(NSString *)pRedrCertId propNo:(NSString *)pPropNo {
flag=@"REBORROW";
self.returnData = [NSMutableData data];
NSString *urlStr = [[NSString alloc]
initWithFormat:@"http://x.x.x.x/libsyswebservices/books.asmx/ReBorrow?redrCertId=%@&propNo=%@",pRedrCertId, pPropNo];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL:
[NSURL URLWithString:urlStr]] delegate:self]; //建立异步连接(多线程)
self.DALConnection = conn;
[conn release]; }
这里采用NSURLConnection对象的异步连接方法——无论获取数据成功或失败,都以委托的方式实现信息的反馈,即通过对反馈信息的识别进行相应处理,而后反馈给Controller层——这就是多线程中反馈信息传递的核心。
(6)以Delegate方式在Model层采用三种方法处理NSURLConnection的三种不同反馈情况,并将结果反馈给Controller。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.returnData appendData:data];
NSLog([[NSString alloc]initWithFormat:@"%@, didReceiveData",flag]); } //1.开始接收数据
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { … } //2.接收失败
- (void)connectionDidFinishLoading:(NSURLConnection *)connection { … } //3.接收成功
第一种是开始接收数据,其余两种分别为接收失败和接收成功。由于DAL中任何请求最终都是由NSURLConnection来处理,但是不同请求得到的反馈是不同的,因此需要在以上三种方法中针对不同的请求进行不同的反馈处理。
(7)Controller接收到Model的反馈后同样以Delegate的方式分别对不同反馈进行相应处理,并进一步反馈到View。
-(void)dal:(DAL *)dal DidFinishReBorrow:(NSString *)string {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"续借" message:[[NSString alloc]
initWithFormat:@"%@,%@",mTitleLabel.text,string] delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release]; }
-(void)dal:(DAL *)dal DidFailReBorrow:(NSError *)error
{
[Utility showSimpleAlert:[[NSString alloc]initWithFormat:@"续借出错,%@",[error localizedDescription]]];
}
(8)Controller通过Outlet修改View中的控件,将提示信息传递给用户。至此,整个处理流程结束。
厦门大学图书馆的iOS客户端于2011年9月18日在APP Store上架,目前已经更新到第三个版本,总下载量为727次。顺应移动互联网发展趋势,厦门大学图书馆将不断完善和拓展移动客户端的功能,充分满足读者对于移动服务的需求。