定位的基础知识:
1、定位芯片和CPU之间通过串口进行通信
2、串口和CPU之间传输的是ASCII格式的NMEA(National Marine Electronics Association)信息,如:
[html] view plain copy
1. $GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F
2. $GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D
3. $GPGSV,3,1,10,20,78,331,45,01,59,235,47,22,41,069,,13,32,252,45*70
4. $GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25
基于以上两点,要探知定位数据从GPS芯片到应用层的流程,最好的途径就是从应用层输出NEMA信息的地方开始。
NMEA资料参见:卫星定位数据NMEA介绍
一、GPS定位的应用层实现
Luckily,在应用层我们可以通过onNmeaReceived()方法获取到NMEA信息,如下Code Fragment:
[java] view plain copy
1. public class GpsTestActivity extends ActionBarActivity {
2. /* Other Codes */
3.
4. /** 获取系统的定位服务,记得在AndroidManifest中赋予定位方面的权限:
5. * <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
6. * <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
7. * <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
8. */
9. LocationManager mLocationService = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
10. mLocationService.addNmeaListener(mNmeaListener);
11.
12. private GpsStatus.NmeaListener mNmeaListener = new NmeaListener() {
13. @Override
14. public void onNmeaReceived(long timestamp, String nmea) {
15. "\n");
16. }
17. };
18. }
二、GPS定位的Framework层实现
GpsStatus.NmeaListener是一个接口类,来自GpsStatus.java文件:
[java] view plain copy
1. frameworks\base\location\java\android\location\GpsStatus.java
2. /**
3. * Used for receiving NMEA sentences from the GPS.
4. * NMEA 0183 is a standard for communicating with marine electronic devices
5. * and is a common method for receiving data from a GPS, typically over a serial port.
6. * See <a href="http://en.wikipedia.org/wiki/NMEA_0183">NMEA 0183</a> for more details.
7. * You can implement this interface and call {@link LocationManager#addNmeaListener}
8. * to receive NMEA data from the GPS engine.
9. */
10. public interface NmeaListener {
11. void onNmeaReceived(long timestamp, String nmea);
12. }
在上述App中,我们的应用程序实现了该方法,一旦NMEA数据到来,onNmeaReceived()方法就被调用一次,我们在Console上可以看到原始的NEMA信息。
那么接下来,就要寻找nmea数据的来源了。
mNmeaListener通过LocationManager类的addNmeaListener()方法进行注册(register):
[java] view plain copy
1. frameworks\base\location\java\android\location\LocationManager.java
2. /**
3. * Adds an NMEA listener.
4. *
5. * @param listener a {@link GpsStatus.NmeaListener} object to register
6. *
7. * @return true if the listener was successfully added
8. *
9. * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
10. */
11. public boolean addNmeaListener(GpsStatus.NmeaListener listener) {
12. boolean result;
13.
14. /* mNmeaListeners是LocationManager类的成员变量:
15. * private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
16. * new HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport>();
17. */
18. if (mNmeaListeners.get(listener) != null) {
19. // listener is already registered
20. return true;
21. }
22. try {
23. new GpsStatusListenerTransport(listener);
24. result = mService.addGpsStatusListener(transport);
25. if (result) {
26. mNmeaListeners.put(listener, transport);
27. }
28. catch (RemoteException e) {
29. "RemoteException in registerGpsStatusListener: ", e);
30. false;
31. }
32.
33. return result;
34. }
这里,先检测定义的NmeaListener有没有被注册过,若果没有,注册之。
注册到哪里去了呢?
由mNmeaListeners成员的定义可知,和GpsStatus.NmeaListener进行关联的是GpsStatusListenerTransport,而它是LocationManager类的一个内部类。
只看相关的部分:
[java] view plain copy
1. // This class is used to send GPS status events to the client's main thread.
2. private class GpsStatusListenerTransport extends IGpsStatusListener.Stub {
3. private final GpsStatus.NmeaListener mNmeaListener;
4.
5. // This must not equal any of the GpsStatus event IDs
6. private static final int NMEA_RECEIVED = 1000;
7. private class Nmea {
8. long mTimestamp;
9. String mNmea;
10.
11. long timestamp, String nmea) {
12. mTimestamp = timestamp;
13. mNmea = nmea;
14. }
15. }
16. private ArrayList<Nmea> mNmeaBuffer;
17.
18. //G psStatusListenerTransport(GpsStatus.Listener listener){}
19. GpsStatusListenerTransport(GpsStatus.NmeaListener listener) {
20. mNmeaListener = listener;
21. null;
22. new ArrayList<Nmea>();
23. }
24.
25. @Override
26. public void onNmeaReceived(long timestamp, String nmea) {
27. if (mNmeaListener != null) {
28. synchronized (mNmeaBuffer) {
29. new Nmea(timestamp, nmea));
30. }
31. Message msg = Message.obtain();
32. msg.what = NMEA_RECEIVED;
33. // remove any NMEA_RECEIVED messages already in the queue
34. mGpsHandler.removeMessages(NMEA_RECEIVED);
35. mGpsHandler.sendMessage(msg);
36. }
37. }
38.
39. private final Handler mGpsHandler = new Handler() {
40. @Override
41. public void handleMessage(Message msg) {
42. if (msg.what == NMEA_RECEIVED) {
43. synchronized (mNmeaBuffer) {
44. int length = mNmeaBuffer.size();
45. for (int i = 0; i < length; i++) {
46. Nmea nmea = mNmeaBuffer.get(i);
47. mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea);
48. }
49. mNmeaBuffer.clear();
50. }
51. else {
52. // synchronize on mGpsStatus to ensure the data is copied atomically.
53. }
54. }
55. }
56. };
57. }
在GpsStatusListenerTransport类中:
定义一个Nmea类型的链表mNmeaBuffer,一旦onNmeaReceived()接收到NMEA数据,新数据被加载到链表mNmeaBuffer中(mNmeaBuffer.add(new Nmea(timestamp, nmea))),然手置消息标志为NMEA_RECEIVED(msg.what = NMEA_RECEIVED)。
mGpsHandler对上述NMEA_RECEIVED消息进行处理,最终把传过来的NMEA数据发往应用层GpsTestActivity中的onNmeaReceived()。
那么,GpsStatusListenerTransport类中onNmeaReceived(long timestamp, String nmea)方法的nmea数据有谁提供呢?
GpsStatusListenerTransport类继承自IGpsStatusListener,由类前的字符"I"我们得知,它是一个扩展名为.aidl的文件。
注:
AIDL:AIDL机制用来完成在进程之间进行通信(在Android中不允许进程间共享数据),它的详细知识另外Google之。
这里,我们再次见到了onNmeaReceived():
[java] view plain copy
1. rameworks\base\location\java\android\location\IGpsStatusListener.aidl
2. oneway interface IGpsStatusListener
3. {
4. void onGpsStarted();
5. void onGpsStopped();
6. void onFirstFix(int ttff);
7. void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, in float[] elevations, in float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask);
8. void onNmeaReceived(long timestamp, String nmea);
9. }
注
:
oneway关键字是用来修饰远程调用行为。使用该关键词时,远程调用不是阻塞的,它只是发送事物数据并立即返回。接口的最终实现是把普通的远程调用按照Binder线程池的调用规则来接收,如果oneway是使用在本地调用上,那么不会有任何影响,并且调用依然是异步的。
下面,探究必须进入第三层。
三、GPS定位的Lib层实现
和IGpsStatusListener接头的是GpsLocationProvider类:
[java] view plain copy
1. frameworks\base\services\java\com\android\server\location\GpsLocationProvider.java
2. public class GpsLocationProvider implements LocationProviderInterface {
3. // 此处省略1000+N行
4. private ArrayList<Listener> mListeners = new ArrayList<Listener>();
5.
6. private final class Listener implements IBinder.DeathRecipient {
7. final IGpsStatusListener mListener;
8.
9. Listener(IGpsStatusListener listener) {
10. mListener = listener;
11. }
12.
13. @Override
14. public void binderDied() {
15. if (DEBUG) Log.d(TAG, "GPS status listener died");
16.
17. synchronized (mListeners) {
18. this);
19. }
20. if (mListener != null) {
21. this, 0);
22. }
23. }
24. }
25.
26. /**
27. * called from native code to report NMEA data received
28. */
29. private void reportNmea(long timestamp) {
30. synchronized (mListeners) {
31. int size = mListeners.size();
32. if (size > 0) {
33. // don't bother creating the String if we have no listeners
34. int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
35. new String(mNmeaBuffer, 0, length);
36.
37. for (int i = 0; i < size; i++) {
38. Listener listener = mListeners.get(i);
39. try {
40. listener.mListener.onNmeaReceived(timestamp, nmea);
41. catch (RemoteException e) {
42. "RemoteException in reportNmea");
43. mListeners.remove(listener);
44. // adjust for size of list changing
45. size--;
46. }
47. }
48. }
49. }
50. }
51. }
GPS定位功能最终需要调用硬件实现,操作硬件就必须通过C/C++完成,GpsLocationProvider中包含许多native方法,采用JNI机制为上层提供服务。
在上面的Code Frame中,通过调用本地方法native_read_nmea()获取到NMEA数据,然后传数据到IGpsStatusListener接口类的onNmeaReceived()方法。
reportNmea()是被JNI方法回调的方法,在 JNI 的实现中,通过这些方法的回调来传递JNI层的执行结果。
源码编译出错,解决问题去。。。
native_read_nmea()在GpsLocationProvider类中定义:
[java] view plain copy
1. private native int native_read_nmea(byte[] buffer, int bufferSize);
native指明它是本地方法,和它对应的C/C++文件的实现是:
[cpp] view plain copy
1. static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jbyteArray nmeaArray, jint buffer_size);
How?Next...
[cpp] view plain copy
1. frameworks\base\services\jni\com_android_server_location_GpsLocationProvider.cpp
2. static JNINativeMethod sMethods[] = {
3. /* name, signature, funcPtr */
4. /* other members... */
5. "native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
6. /* other members... */
7. };
JNINativeMethod是Android中采用的Java和C/C++函数的映射方式,并在其中描述了函数的参数和返回值:
[cpp] view plain copy
1. typedef struct {
2. const char* name; // Java文件中的本地方法
3. const char* signature; // 述了函数的参数和返回值
4. void* fnPtr; // 指针,指向具体的C/C++函数
5. } JNINativeMethod;
详细内容这里还是不展开了。
来看android_location_GpsLocationProvider_read_nmea()的实现:
[cpp] view plain copy
1. static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj,
2. jbyteArray nmeaArray, jint buffer_size)
3. {
4. // this should only be called from within a call to reportNmea
5. jbyte* nmea = (jbyte *)env->GetPrimitiveArrayCritical(nmeaArray, 0);
6. int length = sNmeaStringLength;
7. if (length > buffer_size)
8. length = buffer_size;
9. memcpy(nmea, sNmeaString, length);
10. env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT);
11. return length;
12. }
虽然不清楚JNI深入含义,但这个函数意思还是挺明显的,我们推断:
第5行:用来动态分配内存,nmea指向获取到的内存区域,同时把nmea和nmeaArray进行关联;
第6行:sNmeaStringLength指示一次从串口读取到的字节长度
第7、8行:在Java中调用native_read_nmea()方法时指明了我们需要取的数据长度,所以,如果从串口实际读取的数据长度大于我们需要的,我们对串口数据进行截取:即,只取指定长度的数据;
第9行:从串口读出的数据存在sNmeaString中,这里Copy到nmea指向的内存区域;
第10行:nmea指向的内存区域中的数据交给nmeaArray,然后释放nmea指向的内存空间。这里也可以看到,函数调用是通过nmeaArray传递NMEA数据的
下面应该看sNmeaStringLength、sNmeaString的设置过程:
[cpp] view plain copy
1. static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)
2. {
3. JNIEnv* env = AndroidRuntime::getJNIEnv();
4. // The Java code will call back to read these values
5. // We do this to avoid creating unnecessary String objects
6. sNmeaString = nmea;
7. sNmeaStringLength = length;
8. env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
9. checkAndClearExceptionFromCallback(env, __FUNCTION__);
10. }
method_reportNmea、、、有没有熟悉的感觉?
对,在GpsLocationProvider类中见过reportNmea(long timestamp)函数。
下面的代码片段表明,method_reportNmea()和reportNmea()是绑定在一起的,调用C/C++函数method_reportNmea,也就间接调用Java的reportNmea()方法。这中间的机制,就是JNI!
[cpp] view plain copy
1. static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
2. /* other definitions... */
3. "reportNmea", "(J)V");
4. /* other definitions... */
5. }
而method_reportNmea是在nmea_callback()函数中被调用的,哪里又调用nmea_callback()函数呢?
Let's go to neXt Layer...
四、GPS定位HAL层的实现
所谓Android的HAL层,也就是是Linux的应用程序。至于串口具体配置,比如寄存器配置、数据收发等芯片级实现,是在在Linux内核里的。
com_android_server_location_GpsLocationProvider.cpp文件中另外出现nmea_callback的地方是:
[cpp] view plain copy
1. GpsCallbacks sGpsCallbacks = {
2. sizeof(GpsCallbacks),
3. location_callback,
4. status_callback,
5. sv_status_callback,
6. nmea_callback,
7. set_capabilities_callback,
8. acquire_wakelock_callback,
9. release_wakelock_callback,
10. create_thread_callback,
11. request_utc_time_callback,
12. };
GpsCallbacks结构体封装了所有需要回调的函数(
确切的说是函数指针
),sGpsCallbacks调用关系:
[cpp] view plain copy
1. static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
2. {
3. // this must be set before calling into the HAL library
4. if (!mCallbacksObj)
5. mCallbacksObj = env->NewGlobalRef(obj);
6.
7. // fail if the main interface fails to initialize
8. if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
9. return false;
10.
11. /* other codes */
12. return true;
13. }
而android_location_GpsLocationProvider_init()在GpsLocationProvider类中调用native_init()时被调用:
[cpp] view plain copy
1. static JNINativeMethod sMethods[] = {
2. /* name, signature, funcPtr */
3. "native_init", "()Z", (void*)android_location_GpsLocationProvider_init}
4. }
[cpp] view plain copy
- 这里,我们找到了和上层的关系,和下层如何打交道呢?
- 下面需要贴一大段代码:
[cpp] view plain copy
1. /** Represents the standard GPS interface. */
2. typedef struct {
3. /** set to sizeof(GpsInterface) */
4. size_t size;
5. /**
6. * Opens the interface and provides the callback routines
7. * to the implemenation of this interface.
8. */
9. int (*init)( GpsCallbacks* callbacks );
10. /** Starts navigating. */
11. int (*start)( void );
12. /** Stops navigating. */
13. int (*stop)( void );
14. /** Closes the interface. */
15. void (*cleanup)( void );
16. /** Injects the current time. */
17. int (*inject_time)(GpsUtcTime time, int64_t timeReference,
18. int uncertainty);
19. /** Injects current location from another location provider
20. * (typically cell ID).
21. * latitude and longitude are measured in degrees
22. * expected accuracy is measured in meters
23. */
24. int (*inject_location)(double latitude, double longitude, float accuracy);
25. /**
26. * Specifies that the next call to start will not use the
27. * information defined in the flags. GPS_DELETE_ALL is passed for
28. * a cold start.
29. */
30. void (*delete_aiding_data)(GpsAidingData flags);
31. /**
32. * min_interval represents the time between fixes in milliseconds.
33. * preferred_accuracy represents the requested fix accuracy in meters.
34. * preferred_time represents the requested time to first fix in milliseconds.
35. */
36. int (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,
37. uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);
38. /** Get a pointer to extension information. */
39. const void* (*get_extension)(const char* name);
40. } GpsInterface;
GpsInterface结构体封装了GPS实现的标准接口——接口,注意!接口不就时用来连接两端的吗?一端是com_android_server_location_GpsLocationProvider.cpp文件里的实现,那另一端就是。。。都探到这个地步了,另一端应该是串口方式直接和GPS芯片打交道的Linux驱动了吧?
确是,但是还需要一个媒介:
[cpp] view plain copy
1. struct gps_device_t {
2. struct hw_device_t common;
3. /**
4. * Set the provided lights to the provided values.
5. *
6. * Returns: 0 on succes, error code on failure.
7. */
8. const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
9. };
然后,
[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. /* other codes..*/
5. const**)&module);
6. if (err == 0) {
7. hw_device_t* device;
8. err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
9. if (err == 0) {
10. gps_device_t* gps_device = (gps_device_t *)device;
11. sGpsInterface = gps_device->get_gps_interface(gps_device);
12. }
13. }
14. /* other codes..*/
15. }
16. static JNINativeMethod sMethods[] = {
17. /* name, signature, funcPtr */
18. "class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
19. }
GpsLocationProvider.java通过class_init_native的调用实现对C/C++文件中android_location_GpsLocationProvider_class_init_native的调用;
com_android_server_location_GpsLocationProvider.cpp通过gps_device_t获取操作GPS芯片的接口。How????
重点来了:GPS_HARDWARE_MODULE_ID
对,就是
GPS_HARDWARE_MODULE_ID
!
往下看:
[cpp] view plain copy
1. ardware\qcom\gps\loc_api\libloc_api\gps.c
2. struct hw_module_t HAL_MODULE_INFO_SYM = {
3. .tag = HARDWARE_MODULE_TAG,
4. .version_major = 1,
5. .version_minor = 0,
6. .id = GPS_HARDWARE_MODULE_ID,
7. "loc_api GPS Module",
8. "Qualcomm USA, Inc.",
9. .methods = &gps_module_methods,
10. };
有木有?GPS_HARDWARE_MODULE_ID!
[cpp] view plain copy
1. hardware\qcom\gps\loc_api\libloc_api\gps.c
2. extern const GpsInterface* gps_get_hardware_interface();
3.
4. const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev)
5. {
6. return gps_get_hardware_interface();
7.
8. }
9.
10. static int open_gps(const struct hw_module_t* module, char const* name,
11. struct hw_device_t** device)
12. {
13. struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));
14. sizeof(*dev));
15.
16. dev->common.tag = HARDWARE_DEVICE_TAG;
17. dev->common.version = 0;
18. struct hw_module_t*)module;
19. dev->get_gps_interface = gps__get_gps_interface;
20.
21. struct hw_device_t*)dev;
22. return 0;
23. }
24.
25. static struct hw_module_methods_t gps_module_methods = {
26. .open = open_gps
27. };
流程很清楚了:
gps_get_hardware_interface()函数在驱动程序中实现
——在gps__get_gps_interface()中被调用
——在open_gps()被调用
——在gps_module_methods中例化
——HAL_MODULE_INFO_SYM
const GpsInterface* gps_get_hardware_interface()函数在其他C文件实现,该C文件是和Linux驱动打交道的应用程序。基本功能:
1、open处理器CPU和GPS芯片连接的串口;
2、read串口NEMA数据,并解析;
3、根据上层传进来的回调函数,打包数据,调用相应Callback,进而发送到Android应用层。
[cpp] view plain copy
1. static const GpsInterface mGpsInterface = {
2. sizeof(GpsInterface),
3. .init = gps_init,
4. |--1、接收从上层传下来的GpsCallbacks变量,用它初始化GpsState->callbacks成员
5. |--2、GpsState结构体的其他成员初始化
6. |--3、GpsState->init状态设置为:STATE_INIT
7. |--4、最重要:启动GPS线程,进行数据的读取、处理:
8. thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);
9. --gps_create_thread create_thread_cb;
10. typedef pthread_t (* gps_create_thread)(const char* name, void (*start)(void *), void* arg);
11.
12.
13. .start = gps_start,
14. --设置GPS的状态为开始:GPS_STATUS_SESSION_BEGIN
15. .stop = gps_stop,
16. --设置GPS的状态为结束:GPS_STATUS_SESSION_END
17. .cleanup = gps_cleanup,
18. --退出需要进行的一些清理工作,如GpsState->init = STATE_QUIT,GpsCallbacks指针归null,信号量回收
19. .inject_time = gps_inject_time,
20. --可为空函数
21. .inject_location = gps_inject_location,
22. --可为空函数
23. .delete_aiding_data = gps_delete_aiding_data,
24. --可为空函数
25. .set_position_mode = gps_set_position_mode,
26. --设置GPS工作模式:单GPS、单BD、GPS/BD双系统
27. .get_extension = gps_get_extension,
28. --定位之外的扩展功能实现
29. };
30.
31. state->thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);
32. static void gps_state_thread(void* arg):
33. 1、state通过arg参数传入函数
34. 2、创建了Time和Nmea数据处理两个线程
35. "nmea_thread", gps_nmea_thread, state);
36. static void gps_nmea_thread(void* arg)
37. --gps_opentty(state);
38. nmea_reader_init(reader);
39. --nmea_reader_parse(NmeaReader* r) {
40. if (gps_state->callbacks.nmea_cb) {
41. struct timeval tv;
42. long long mytimems;
43. gettimeofday(&tv,NULL);
44. mytimems = tv.tv_sec * 1000 + tv.tv_usec / 1000;
45. gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);
46. "reader_parse. %.*s ", r->pos, r->in );
47. }
48. }
我们是从APP层NMEA信息输出自定向下分析的,APP层信息输出的最终起始是:gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);
到这里还有个问题:GPS芯片和CPU连接,使用的是哪个串口?这个串口号怎么确定的呢?
打算贴个完整HAL层的实例,考虑到代码很多,下篇在说吧。。