好久没有写技术博客了,恰逢今天还感冒了,这破天气,晚上凉风一吹,就感冒了,要加强锻炼呀。

好了,废话不多说,由于工作需要,我要移植一个虚拟的gps模块,于是乎,我就参考了android模拟器的gps模块的实现方法,只需稍微改动就完成了我的工作了,随后我也会附上我做的模块的代码,这里主要还是来解析下模拟器上的gps模块代码吧。

相信做过android location方面应用的同志都知道,android 模拟器虽然没有真正的GPS功能,但是DDMS可以模拟GPS,通过telnet连接到adb,然后发送GPS数据,再转化成NMEA格式的信号给android系统,就可以模拟出location功能了,相信用过的童鞋都知道,没用过的同志去搜索一下就知道了,这里我就不多说了,我主要还是来分析一下这个模拟的功能是如何实现的,这里还是膜拜一下写android源码的大神们,多看看源码,学到的东西很多呢。

首先,我们直入主题,对于移植系统的人来说(比如说我),关注的是中间部分的代码,android的framework层我们需要改动的很少,最多就是加点log来调试,驱动层呢,因为模拟器没有真实的设备,也不可能利用PC上的资源区模拟,因为PC是没有GPS模块的(除非你的电脑很高级),但是我想还是可以通过网络来得到地理位置的,虽然不是非常的准确,希望google的工程师可以去完善,呵呵,题外话了。说了这么多,我就是想说,android 模拟器中gps模块的功能主要依赖于2个东西,一个是ddms中的geo fix命令,还有一个是hal层中的gps_qemu.c中作为硬件抽象层的处理,把虚拟的数据上报给framework层。

主要层次如下图


android模拟gps数据 安卓gps模块_android模拟gps数据

好了,思路清晰了,咱就看代码,位于源码目录下/sdk/emulator/gps/gps_qemu.c

首先我们要搞清楚,在andrroid中HAL 的一个位置问题,HAL是为了更好的封装好硬件驱动存在的,主要是一些接口,编译成库文件,给framework中国的jni来调用,我们这里的GPS模块会被编译成gps.goldfish.so文件,在同目录下的Android.mk中有写到


[cpp]  
   view plain 
   copy 
   
 
   
 
 
1. LOCAL_CFLAGS += -DQEMU_HARDWARE  
2. LOCAL_SHARED_LIBRARIES := liblog libcutils libhardware  
3. LOCAL_SRC_FILES := gps_qemu.c  
4. LOCAL_MODULE := gps.goldfish  
5. LOCAL_MODULE_TAGS := debug  
 

然后呢,在jni中会这样调用 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {  
2. int err;  
3.     hw_module_t* module;  
4.   
5. "reportLocation", "(IDDDFFFJ)V");  
6. "reportStatus", "(I)V");  
7. "reportSvStatus", "()V");  
8. "reportAGpsStatus", "(III)V");  
9. "reportNmea", "(J)V");  
10. "setEngineCapabilities", "(I)V");  
11. "xtraDownloadRequest", "()V");  
12. "reportNiNotification",  
13. "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");  
14. "requestRefLocation","(I)V");  
15. "requestSetID","(I)V");  
16. "requestUtcTime","()V");  
17.   
18. const**)&module);  
19. if (err == 0) {  
20.         hw_device_t* device;  
21.         err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);  
22. if (err == 0) {  
23.             gps_device_t* gps_device = (gps_device_t *)device;  
24.             sGpsInterface = gps_device->get_gps_interface(gps_device);  
25.         }  
26.     }  
27. if (sGpsInterface) {  
28.         sGpsXtraInterface =  
29. const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);  
30.         sAGpsInterface =  
31. const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);  
32.         sGpsNiInterface =  
33. const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);  
34.         sGpsDebugInterface =  
35. const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);  
36.         sAGpsRilInterface =  
37. const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);  
38.     }  
39. }  
 
这个函数在android设备启动的时候会被调用来初始化GPS模块的一些东西,主要是来的到GPS模块的一些接口函数,重点看这个函数 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);  
 

这个函数原型在HAL中的hardware.c中 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. int hw_get_module_by_class(const char *class_id, const char *inst,  
2. const struct hw_module_t **module)  
3. {  
4. int status;  
5. int i;  
6. const struct hw_module_t *hmi = NULL;  
7. char prop[PATH_MAX];  
8. char path[PATH_MAX];  
9. char name[PATH_MAX];  
10.   
11. if (inst)  
12. "%s.%s", class_id, inst);  
13. else  
14.         strlcpy(name, class_id, PATH_MAX);  
15.   
16. /*
17.      * Here we rely on the fact that calling dlopen multiple times on
18.      * the same .so will simply increment a refcount (and not load
19.      * a new copy of the library).
20.      * We also assume that dlopen() is thread-safe.
21.      */  
22.   
23. /* Loop through the configuration variants looking for a module */  
24. for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {  
25. if (i < HAL_VARIANT_KEYS_COUNT) {  
26. if (property_get(variant_keys[i], prop, NULL) == 0) {  
27. continue;  
28.             }  
29. sizeof(path), "%s/%s.%s.so",  
30.                      HAL_LIBRARY_PATH2, name, prop);  
31. if (access(path, R_OK) == 0) break;  
32.   
33. sizeof(path), "%s/%s.%s.so",  
34.                      HAL_LIBRARY_PATH1, name, prop);  
35. if (access(path, R_OK) == 0) break;  
36. else {  
37. sizeof(path), "%s/%s.default.so",  
38.                      HAL_LIBRARY_PATH1, name);  
39. if (access(path, R_OK) == 0) break;  
40.         }  
41.     }  
42.   
43.     status = -ENOENT;  
44. if (i < HAL_VARIANT_KEYS_COUNT+1) {  
45. /* load the module, if this fails, we're doomed, and we should not try
46.          * to load a different variant. */  
47.         status = load(class_id, path, module);  
48.     }  
49.   
50. return status;  
51. }  
 

当我们编译gps模块之后会在/system/lib/hw/下生成一个gps.goldfish.so文件,这个函数就是去寻找这个库文件,然后调用load函数去打开这个库文件,来得到库中的函数接口 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static int load(const char *id,  
2. const char *path,  
3. const struct hw_module_t **pHmi)  
4. {  
5. int status;  
6. void *handle;  
7. struct hw_module_t *hmi;  
8.   
9. /*
10.      * load the symbols resolving undefined symbols before
11.      * dlopen returns. Since RTLD_GLOBAL is not or'd in with
12.      * RTLD_NOW the external symbols will not be global
13.      */  
14.     handle = dlopen(path, RTLD_NOW);  
15. if (handle == NULL) {  
16. char const *err_str = dlerror();  
17. "load: module=%s\n%s", path, err_str?err_str:"unknown");  
18.         status = -EINVAL;  
19. goto done;  
20.     }  
21.   
22. /* Get the address of the struct hal_module_info. */  
23. const char *sym = HAL_MODULE_INFO_SYM_AS_STR;  
24. struct hw_module_t *)dlsym(handle, sym);  
25. if (hmi == NULL) {  
26. "load: couldn't find symbol %s", sym);  
27.         status = -EINVAL;  
28. goto done;  
29.     }  
30.   
31. /* Check that the id matches */  
32. if (strcmp(id, hmi->id) != 0) {  
33. "load: id=%s != hmi->id=%s", id, hmi->id);  
34.         status = -EINVAL;  
35. goto done;  
36.     }  
37.   
38.     hmi->dso = handle;  
39.   
40. /* success */  
41.     status = 0;  
42.   
43.     done:  
44. if (status != 0) {  
45.         hmi = NULL;  
46. if (handle != NULL) {  
47.             dlclose(handle);  
48.             handle = NULL;  
49.         }  
50. else {  
51. "loaded HAL id=%s path=%s hmi=%p handle=%p",  
52.                 id, path, *pHmi, handle);  
53.     }  
54.   
55.     *pHmi = hmi;  
56.   
57. return status;  
58. }  
 

这里我介绍的比较简洁,因为在我之前的博客中已经介绍过这部分的内容了,可以参考这里: 
 
 
 
 好了,回到我们GPS模块的代码上来
 之后就会调用
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);  
 

来打开设备,来看下HAL中的代码 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static int open_gps(const struct hw_module_t* module, char const* name,  
2. struct hw_device_t** device)  
3. {  
4. struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));  
5. sizeof(*dev));  
6.   
7.     dev->common.tag = HARDWARE_DEVICE_TAG;  
8.     dev->common.version = 0;  
9. struct hw_module_t*)module;  
10. //    dev->common.close = (int (*)(struct hw_device_t*))close_lights;  
11.     dev->get_gps_interface = gps__get_gps_interface;  
12.   
13. struct hw_device_t*)dev;  
14. return 0;  
15. }  
 

这里只是做了一些初始化,然后把接口函数挂钩一下 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. dev->get_gps_interface = gps__get_gps_interface;  
 

这个回调函数很简单 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static const GpsInterface  qemuGpsInterface = {  
2. sizeof(GpsInterface),  
3.     qemu_gps_init,  
4.     qemu_gps_start,  
5.     qemu_gps_stop,  
6.     qemu_gps_cleanup,  
7.     qemu_gps_inject_time,  
8.     qemu_gps_inject_location,  
9.     qemu_gps_delete_aiding_data,  
10.     qemu_gps_set_position_mode,  
11.     qemu_gps_get_extension,  
12. };  
13.   
14. const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev)  
15. {  
16. return &qemuGpsInterface;  
17. }  
 

返回qemuGpsInterface结构体,这个机构提中就是一大堆的回调函数。 
 
 下面我们按照调用顺序来一个一个介绍这些回调函数。
 首先就是qume_gps_init函数
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static int  
2. qemu_gps_init(GpsCallbacks* callbacks)  
3. {  
4.     GpsState*  s = _gps_state;  
5.   
6. if (!s->init)  
7.         gps_state_init(s, callbacks);  
8.   
9. if (s->fd < 0)  
10. return -1;  
11.   
12. return 0;  
13. }  
 

这里我发现了一个很好玩的东西,这里这个GpsState* s是如何得到全局的实例的呢,是通过_gps_state,而_gps_state的定义是这样的 
 
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. typedef struct {  
2. int                     init;  
3. int                     fd;  
4.     GpsCallbacks            callbacks;  
5. thread;  
6. int                     control[2];  
7. } GpsState;  
8.   
9. static GpsState  _gps_state[1];  
 

这里我的理解是在全局静态的定义了一个结构体指针,并分配了内存。 
 
 为何不在init函数中使用malloc来分配内存,然后使用呢,有点意思,现在还不知道有什么好处,难道只是卖弄吗?
 好了,不多说了,接下去看调用的gps_state_init函数
 在这之前,我来介绍下GpsState结构体中成员的作用吧
 int init:
 一个初始化的标志,为1表示初始化了,为0表示未初始化
 int fd:
 socket读写的文件描述符,如果是真实的硬件的话,应该是串口读写的描述符
 callbacks:
 这个是从jni传下来的回调函数,得到数据之后就回调
 thread:
 这个没什么好说的,就是一个线程
 int control[2]:
 本地使用的socket来进程间通信,会面会讲到。
 继续init函数
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static void  
2. gps_state_init( GpsState*  state, GpsCallbacks* callbacks )  
3. {  
4.     state->init       = 1;  
5.     state->control[0] = -1;  
6.     state->control[1] = -1;  
7.     state->fd         = -1;  
8.   
9.     state->fd = qemud_channel_open(QEMU_CHANNEL_NAME);  
10.   
11. if (state->fd < 0) {  
12. "no gps emulation detected");  
13. return;  
14.     }  
15.   
16. "gps emulation will read from '%s' qemud channel", QEMU_CHANNEL_NAME );  
17.   
18. if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) {  
19. "could not create thread control socket pair: %s", strerror(errno));  
20. goto Fail;  
21.     }  
22.   
23. thread = callbacks->create_thread_cb( "gps_state_thread", gps_state_thread, state );  
24.   
25. if ( !state->thread ) {  
26. "could not create gps thread: %s", strerror(errno));  
27. goto Fail;  
28.     }  
29.   
30.     state->callbacks = *callbacks;  
31.   
32. "gps state initialized");  
33. return;  
34.   
35. Fail:  
36.     gps_state_done( state );  
37. }


首先书初始化赋值工作,看到没,把init变量赋值为1了。然后调用了qemud_channel_open函数来得到了adb tcp的socket文件描述符。然后调用socketpair创建本地的socket通信对来实现进程间通信,然后创建了线程,赋值回调函数,下图描述了代码执行的流程。


android模拟gps数据 安卓gps模块_java_02

这图有点丑,不过大体思路还是清楚的,可以对照着代码看,这里使用的是event poll技术进行事件的处理,在线程中,把fd和control[1]加入了epoll中,设置为POLLIIN模式,当有事件发生是,就会调用相应的代码,这里的control[1],在这里做控制作用,只要是控制gps的开始和停止的,所以在线程外面对control[0]进行写操作的话,对应的control[1]就会收到相应的指令,然后采取措施。具体代码如下


[cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static void  
2. gps_state_thread( void*  arg )  
3. {  
4.     GpsState*   state = (GpsState*) arg;  
5.     NmeaReader  reader[1];  
6. int         epoll_fd   = epoll_create(2);  
7. int         started    = 0;  
8. int         gps_fd     = state->fd;  
9. int         control_fd = state->control[1];  
10.   
11.     nmea_reader_init( reader );  
12.   
13. // register control file descriptors for polling  
14.     epoll_register( epoll_fd, control_fd );  
15.     epoll_register( epoll_fd, gps_fd );  
16.   
17. "gps thread running");  
18.   
19. // now loop  
20. for (;;) {  
21. struct epoll_event   events[2];  
22. int                  ne, nevents;  
23.   
24.         nevents = epoll_wait( epoll_fd, events, 2, -1 );  
25. if (nevents < 0) {  
26. if (errno != EINTR)  
27. "epoll_wait() unexpected error: %s", strerror(errno));  
28. continue;  
29.         }  
30. "gps thread received %d events", nevents);  
31. for (ne = 0; ne < nevents; ne++) {  
32. if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) {  
33. "EPOLLERR or EPOLLHUP after epoll_wait() !?");  
34. return;  
35.             }  
36. if ((events[ne].events & EPOLLIN) != 0) {  
37. int  fd = events[ne].data.fd;  
38.   
39. if (fd == control_fd)  
40.                 {  
41. char  cmd = 255;  
42. int   ret;  
43. "gps control fd event");  
44. do {  
45.                         ret = read( fd, &cmd, 1 );  
46. while (ret < 0 && errno == EINTR);  
47.   
48. if (cmd == CMD_QUIT) {  
49. "gps thread quitting on demand");  
50. return;  
51.                     }  
52. else if (cmd == CMD_START) {  
53. if (!started) {  
54. "gps thread starting  location_cb=%p", state->callbacks.location_cb);  
55.                             started = 1;  
56.                             nmea_reader_set_callback( reader, state->callbacks.location_cb );  
57.                         }  
58.                     }  
59. else if (cmd == CMD_STOP) {  
60. if (started) {  
61. "gps thread stopping");  
62.                             started = 0;  
63.                             nmea_reader_set_callback( reader, NULL );  
64.                         }  
65.                     }  
66.                 }  
67. else if (fd == gps_fd)  
68.                 {  
69. char  buff[32];  
70. "gps fd event");  
71. for (;;) {  
72. int  nn, ret;  
73.   
74. sizeof(buff) );  
75. if (ret < 0) {  
76. if (errno == EINTR)  
77. continue;  
78. if (errno != EWOULDBLOCK)  
79. "error while reading from gps daemon socket: %s:", strerror(errno));  
80. break;  
81.                         }  
82. "received %d bytes: %.*s", ret, ret, buff);  
83. for (nn = 0; nn < ret; nn++)  
84.                             nmea_reader_addc( reader, buff[nn] );  
85.                     }  
86. "gps fd event end");  
87.                 }  
88. else  
89.                 {  
90. "epoll_wait() returned unkown fd %d ?", fd);  
91.                 }  
92.             }  
93.         }  
94.     }  
95. }  
 


 
 好了,android 模拟器的虚拟hal层就介绍到这边,下面来看一下geo fix命令的实现源码,我也是找了好久才找到的,在external/qemu/android/console.c中
 
 
 
   [cpp]  
   view plain 
   copy 
   
 
   
 
 
1. static int  
2. do_geo_fix( ControlClient  client, char*  args )  
3. {  
4. // GEO_SAT2 provides bug backwards compatibility.  
5. enum { GEO_LONG = 0, GEO_LAT, GEO_ALT, GEO_SAT, GEO_SAT2, NUM_GEO_PARAMS };  
6. char*   p = args;  
7. int     top_param = -1;  
8. double  params[ NUM_GEO_PARAMS ];  
9. int     n_satellites = 1;  
10.   
11. static  int     last_time = 0;  
12. static  double  last_altitude = 0.;  
13.   
14. if (!p)  
15. "";  
16.   
17. /* tokenize */  
18. while (*p) {  
19. char*   end;  
20. double  val = strtod( p, &end );  
21.   
22. if (end == p) {  
23. "KO: argument '%s' is not a number\n", p );  
24. return -1;  
25.         }  
26.   
27.         params[++top_param] = val;  
28. if (top_param + 1 == NUM_GEO_PARAMS)  
29. break;  
30.   
31.         p = end;  
32. while (*p && (p[0] == ' ' || p[0] == '\t'))  
33.             p += 1;  
34.     }  
35.   
36. /* sanity check */  
37. if (top_param < GEO_LAT) {  
38. "KO: not enough arguments: see 'help geo fix' for details\r\n" );  
39. return -1;  
40.     }  
41.   
42. /* check number of satellites, must be integer between 1 and 12 */  
43. if (top_param >= GEO_SAT) {  
44. int sat_index = (top_param >= GEO_SAT2) ? GEO_SAT2 : GEO_SAT;  
45. int) params[sat_index];  
46. if (n_satellites != params[sat_index]  
47.             || n_satellites < 1 || n_satellites > 12) {  
48. "KO: invalid number of satellites. Must be an integer between 1 and 12\r\n");  
49. return -1;  
50.         }  
51.     }  
52.   
53. /* generate an NMEA sentence for this fix */  
54.     {  
55.         STRALLOC_DEFINE(s);  
56. double   val;  
57. int      deg, min;  
58. char     hemi;  
59.   
60. /* format overview:
61.          *    time of fix      123519     12:35:19 UTC
62.          *    latitude         4807.038   48 degrees, 07.038 minutes
63.          *    north/south      N or S
64.          *    longitude        01131.000  11 degrees, 31. minutes
65.          *    east/west        E or W
66.          *    fix quality      1          standard GPS fix
67.          *    satellites       1 to 12    number of satellites being tracked
68.          *    HDOP             <dontcare> horizontal dilution
69.          *    altitude         546.       altitude above sea-level
70.          *    altitude units   M          to indicate meters
71.          *    diff             <dontcare> height of sea-level above ellipsoid
72.          *    diff units       M          to indicate meters (should be <dontcare>)
73.          *    dgps age         <dontcare> time in seconds since last DGPS fix
74.          *    dgps sid         <dontcare> DGPS station id
75.          */  
76.   
77. /* first, the time */  
78. "$GPGGA,%06d", last_time );  
79.         last_time ++;  
80.   
81. /* then the latitude */  
82. 'N';  
83.         val  = params[GEO_LAT];  
84. if (val < 0) {  
85. 'S';  
86.             val  = -val;  
87.         }  
88. int) val;  
89.         val = 60*(val - deg);  
90. int) val;  
91.         val = 10000*(val - min);  
92. ",%02d%02d.%04d,%c", deg, min, (int)val, hemi );  
93.   
94. /* the longitude */  
95. 'E';  
96.         val  = params[GEO_LONG];  
97. if (val < 0) {  
98. 'W';  
99.             val  = -val;  
100.         }  
101. int) val;  
102.         val = 60*(val - deg);  
103. int) val;  
104.         val = 10000*(val - min);  
105. ",%02d%02d.%04d,%c", deg, min, (int)val, hemi );  
106.   
107. /* bogus fix quality, satellite count and dilution */  
108. ",1,%02d,", n_satellites );  
109.   
110. /* optional altitude + bogus diff */  
111. if (top_param >= GEO_ALT) {  
112. ",%.1g,M,0.,M", params[GEO_ALT] );  
113.             last_altitude = params[GEO_ALT];  
114. else {  
115. ",,,," );  
116.         }  
117. /* bogus rest and checksum */  
118. ",,,*47" );  
119.   
120. /* send it, then free */  
121.         android_gps_send_nmea( stralloc_cstr(s) );  
122.         stralloc_reset( s );  
123.     }  
124. return 0;  
125. }


通过穿进去的经纬度,海拔等信息转化成NMEA格式的gps数据,然后通过socket发出去。

这部分就介绍到这里,之后会更精彩,哈哈。

希望这篇文章对读者有帮助,完全是参考android源码的,对我来说源码是最好的学习途径。