引言
随着社会发展和科技进步,人们在日常生活中对于远程定位的需求越来越强烈,尤其在登山、家庭监护、宠物看管等应用场合,远程定位扮演着十分重要的角色[1]。本文根据远程定位的应用场景及需求特点,基于Android4.4版本[2]和百度地图开放API[3]设计了一套可以同时支持多个定位与接收显示终端的远程位置定位系统,用户可以通过Android终端查看当前绑定了相应定位终端的具体位置,位置信息由定位终端发出。文中主要阐述了服务器端程序架构的设计以及如何实现多用户支持的具体细节,并且结合Android4.4版本的特点,通过深入解析Android内核源码,描述了Looper机制以及系统如何将消息在线程间进行传递,最后,根据百度提供的API,详细说明了如何通过从服务器接收的位置消息实现准确的定位。实验结果表明,系统运行稳定、定位精确度满足要求。系统框架如图1所示。
图1 系统总体结构
1 服务器模块设计
从系统总体结构图可以看出,本系统使用一台服务器同时服务若干用户,所以服务器的结构设计对于系统能否正常稳定运行起到至关重要的作用。
1.1 总体框架设计
为了让消息可以有序的被处理,本系统的服务器程序采用消息队列机制[4]。设置了3个消息队列,分别是服务定位终端的定位消息队列posMessageQueue、服务Android接收端的接收消息队列recMessageQueue,以及进行消息处理与调度的主消息队列mainMessageQueue,同时在程序运行的时候开启了5个线程,见表1。
表1 服务器系统线程列表
从表1中可以看到,pos_read和rec_read线程都是监听socket端口,如果有相关消息,则接收并发送到对应的消息队列中。本系统监听接收消息采用以下函数:
socket_recv_from(const int sock_fd, char *buff, const int max_len, const int flags,struct sockaddr *pcliaddr, socklen_t *clilen)
其中,sockaddr表示socket地址和端口信息。
系统中接收到消息后将消息发送到消息队列采用以下函数:
sndMessageQueue(int msgID, void *pData, unsigned long dwSize,long mtype)
其中,msgID表示消息队列的ID号,如为主消息队列,则直接用main_que_id定义一个int类型的数。
线程pos_write和rec_write监听消息队列,并从消息队列接收消息,系统的实现采用以下函数:
rcvMessageQueue(int msgID, void *pData, unsigned long dwSize, int isBlock)
其中参数与sndMessageQueue一致。
系统服务器程序的框架如图2所示。
CEO线程从主消息队列中接收的消息经过处理后发送到recMessageQueue和posMessageQueue,然后再将消息通过对应线程和socket端口发送出去。系统采用这样的设计方式,可以使得在有多个终端同时工作的时候,信息得到稳定高效的处理。
图2 服务器系统框架
1.2 系统终端注册机制和主线程设计
系统需要同时满足多个用户的定位需求,每个用户对应一个定位终端和Android终端,这样不同的定位终端发过来的消息需要找到对应的Android终端来接收。从图2可以看到,所有的消息都是通过3个消息队列和5个线程来接收、处理和转发的。当不同的消息进入同一个消息队列中,系统需要知道消息是由哪个用户发出的,这样终端在开始发送和接收消息的时候首先需要进行配对,也就是在服务器上面注册用户信息。
本系统定义了5种消息类型,如表2所列。
表2 服务器系统消息列表
其中注册类消息用来完成终端注册,心跳消息用来通知服务器终端是否还在工作状态,数据消息用来传递具体的位置信息数据。对于回复OK的消息,因为本系统采用的是UDP方式通信,是无连接的,非安全性通信,所以对于终端发送过来的消息,服务器需要发送一个确定接收的消息,这个消息本系统定义为回复OK的消息。如果消息传递错误,终端就会发送错误消息到服务器。
除了这5种消息,系统还定义了终端类型,如表3所列。
表3 终端类型列表
另外为了对消息进行管理,定义了三个结构体。
终端管理结构体:
typedef struct _TrmMng{
intpos_fd;//定位终端所注册的网络套接字
intrec_fd;//Android节点所注册的网络套接字
char trm_num;//终端编号
}TrmMng;
终端端口地址结构体:
typedef struct _AddrMng{
struct sockaddr_in cliaddr;//客户端地址和端口相关信息
socklen_t clilen;//cliaddr的长度,sendto函数的参数
int index;//在当前数组的位置
}AddrMng;
消息体结构体:
typedef struct _MsgBuf{
long mtype;//消息类型
char mtext[MAX_MSGSIZE];
} MsgBuf;
在结构体的基础上定义了TrmMng trm_mng[MAX_TRM_NUM]和AddrMng addr_mng[MAX_TRM_NUM * 2]这两个数组来进行统一的终端和客户端地址的管理。
在系统开始的时候,无论是定位终端还是Android终端,向服务器发送的消息都有两部分:一部分是包含IP地址和端口号,由服务器自动保存;另外一部分是终端号类型。注册过程略——编者注。
系统中服务器端对接收到的各种消息的处理是在CEO线程中进行的,它根据不同的消息类型进行不同的处理,CEO线程也是唯一可以和3个消息队列进行信息交流的线程。它从主消息队列中取消息,一般采用阻塞监听的方式,无消息时线程阻塞,有消息时线程唤醒,读取消息、解析处理后发送到另外两个消息队列中,主线程的设计框架如图3所示。
图3 主线程框架
2 Android端设计
本系统位置显示终端采用的是基于Android的移动终端或者中控节点,带有位置信息的消息通过定位终端采集后,上传到服务器,然后通过服务器配对,传到指定的接收端。Android端在接收到消息后,先将消息通过线程间传递机制传到Looper的MessageQueue,然后由百度地图提供的API获取后进行定位。所以Android端设计分为两个部分:消息传递模块和百度地图定位模块。
2.1 消息传递模块
对于Android端的位置消息主要通过socket方式从服务器获取。本系统采用Android4.4版本作为其操作系统。由于在Android4.4版本中,主线程中是不能有网络相关操作的,因此采用这种设计方式可以避免因为网络操作造成的Activity线程等待,提高用户体验。而与此同时,主线程中与网络通信相关的操作就要用另外一个线程去处理,处理完成后将处理结果传递给主线程,这个传递机制就是Android中使用很广泛的Looper机制,采用这种方式也解决了Android中很经典的非主线程不能更新UI的问题。因为在本系统中的socket操作属于网络操作,所以不能直接在地图UI中启动socket接收位置消息,需要另外启动一个线程,专门用来接收位置消息和对消息进行解析,然后通过Looper机制传递给地图线程,用来更新当前位置。
Looper机制和系统位置信息传递原理略——编者注。
2.2 百度地图定位模块
本系统采用的定位服务是基于百度地图API移动版本的,它支持Android设备应用程序接口,通过这些API可以构建强交互性的应用,同时还可以实现定位、本地搜索、路线规划等数据服务[5]。
百度地图应用程序的开发可以通过设置布局文件权限、添加jar包以及显示百度地图的控件等几个步骤实现。其中有一些比较重要的类和对象见表4。
表4 百度地图类和对象列表
本系统百度地图中的位置显示分为3个部分,分别是初始化地图initMap()、定位中心位置moveToPoint(cLat, cLon),以及显示当前位置图层showCurtainPoint(double cLat,double cLon)。
initMap()是在地图线程开始执行的,而在Looper消息队列的处理函数handleMessage中放置了另外两个函数moveToPoint(cLat, cLon)和showCurtainPoint(double cLat,double cLon)。这样当Looper对象调用loop()方法后,就可以循环执行这两个函数,从而达到对地图的实时更新。具体流程略——编者注。
3 系统测试
本系统自定义通信协议(协议略),在服务器端接收到从定位终端发送的位置信息处理后,发送到Android终端,再对通信协议进行解析,调用百度地图服务进行处理,将其显示在Android终端,可以实现实时获取与显示地理位置的功能。在Android端的显示效果略——编者注。
结语
随着目前智能硬件的发展热潮和我国自主研发的北斗定位系统的普及,远程定位系统给人们日常生活带来了很多便利。本文采用的多线程和消息队列的方式,不仅可以为多个用户提供同时通信服务,而且还可以应用于很多领域。在Android4.4版本中实现位置信息线程间传递和百度地图的应用,对于其他的位置消息传递应用也有很好的借鉴作用,可以在百度地图的基础上进行功能扩展。