您的位置:首页 > 运维架构 > 网站架构

【Android架构GPS篇】之定位数据如何从GPS芯片到应用层

2016-12-20 19:05 597 查看
原址:http://blog.csdn.net/u013686019/article/details/47444839

写在前面

在漫长的Android源码编译等待过程中,想起之前写过一部分的Android定位实现的探究小品,于是继续探究。
:代码都是片段化的代码,用来提纲挈领的说明问题。

定位的基础知识

1、定位芯片和CPU之间通过串口进行通信

2、串口和CPU之间传输的是ASCII格式的NMEA(National Marine Electronics Association)信息,如:

[html] view
plain copy

print?





$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F

$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D

$GPGSV,3,1,10,20,78,331,45,01,59,235,47,22,41,069,,13,32,252,45*70

$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

print?





public class GpsTestActivity extends ActionBarActivity {

/* Other Codes */

/** 获取系统的定位服务,记得在AndroidManifest中赋予定位方面的权限:

* <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

* <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>

* <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

*/

LocationManager mLocationService = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

mLocationService.addNmeaListener(mNmeaListener);

private GpsStatus.NmeaListener mNmeaListener = new NmeaListener() {

@Override

public void onNmeaReceived(long timestamp, String nmea) {

System.out.println(nmea + "\n");

}

};

}

二、GPS定位的Framework层实现

GpsStatus.NmeaListener是一个接口类,来自GpsStatus.Java文件:

[java] view
plain copy

print?





frameworks\base\location\java\android\location\GpsStatus.java

/**

* Used for receiving NMEA sentences from the GPS.

* NMEA 0183 is a standard for communicating with marine electronic devices

* and is a common method for receiving data from a GPS, typically over a serial port.

* See <a href="http://en.wikipedia.org/wiki/NMEA_0183">NMEA 0183</a> for more details.

* You can implement this interface and call {@link LocationManager#addNmeaListener}

* to receive NMEA data from the GPS engine.

*/

public interface NmeaListener {

void onNmeaReceived(long timestamp, String nmea);

}

在上述App中,我们的应用程序实现了该方法,一旦NMEA数据到来,onNmeaReceived()方法就被调用一次,我们在Console上可以看到原始的NEMA信息。

那么接下来,就要寻找nmea数据的来源了。

mNmeaListener通过LocationManager类的addNmeaListener()方法进行注册(register):

[java] view
plain copy

print?





frameworks\base\location\java\android\location\LocationManager.java

/**

* Adds an NMEA listener.

*

* @param listener a {@link GpsStatus.NmeaListener} object to register

*

* @return true if the listener was successfully added

*

* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present

*/

public boolean addNmeaListener(GpsStatus.NmeaListener listener) {

boolean result;

/* mNmeaListeners是LocationManager类的成员变量:

* private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =

* new HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport>();

*/

if (mNmeaListeners.get(listener) != null) {

// listener is already registered

return true;

}

try {

GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);

result = mService.addGpsStatusListener(transport);

if (result) {

mNmeaListeners.put(listener, transport);

}

} catch (RemoteException e) {

Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e);

result = false;

}

return result;

}

这里,先检测定义的NmeaListener有没有被注册过,若果没有,注册之。

注册到哪里去了呢?

由mNmeaListeners成员的定义可知,和GpsStatus.NmeaListener进行关联的是GpsStatusListenerTransport,而它是LocationManager类的一个内部类。

只看相关的部分:

[java] view
plain copy

print?





// This class is used to send GPS status events to the client's main thread.

private class GpsStatusListenerTransport extends IGpsStatusListener.Stub {

private final GpsStatus.NmeaListener mNmeaListener;

// This must not equal any of the GpsStatus event IDs

private static final int NMEA_RECEIVED = 1000;

private class Nmea {

long mTimestamp;

String mNmea;

Nmea(long timestamp, String nmea) {

mTimestamp = timestamp;

mNmea = nmea;

}

}

private ArrayList<Nmea> mNmeaBuffer;

//G psStatusListenerTransport(GpsStatus.Listener listener){}

GpsStatusListenerTransport(GpsStatus.NmeaListener listener) {

mNmeaListener = listener;

mListener = null;

mNmeaBuffer = new ArrayList<Nmea>();

}

@Override

public void onNmeaReceived(long timestamp, String nmea) {

if (mNmeaListener != null) {

synchronized (mNmeaBuffer) {

mNmeaBuffer.add(new Nmea(timestamp, nmea));

}

Message msg = Message.obtain();

msg.what = NMEA_RECEIVED;

// remove any NMEA_RECEIVED messages already in the queue

mGpsHandler.removeMessages(NMEA_RECEIVED);

mGpsHandler.sendMessage(msg);

}

}

private final Handler mGpsHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

if (msg.what == NMEA_RECEIVED) {

synchronized (mNmeaBuffer) {

int length = mNmeaBuffer.size();

for (int i = 0; i < length; i++) {

Nmea nmea = mNmeaBuffer.get(i);

mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea);

}

mNmeaBuffer.clear();

}

} else {

// synchronize on mGpsStatus to ensure the data is copied atomically.

}

}

}

};

}

在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

print?





frameworks\base\location\java\android\location\IGpsStatusListener.aidl

oneway interface IGpsStatusListener

{

void onGpsStarted();

void onGpsStopped();

void onFirstFix(int ttff);

void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, in float[] elevations, in float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask);

void onNmeaReceived(long timestamp, String nmea);

}



oneway关键字是用来修饰远程调用行为。使用该关键词时,远程调用不是阻塞的,它只是发送事物数据并立即返回。接口的最终实现是把普通的远程调用按照Binder线程池的调用规则来接收,如果oneway是使用在本地调用上,那么不会有任何影响,并且调用依然是异步的。

下面,探究必须进入第三层。

三、GPS定位的Lib层实现

和IGpsStatusListener接头的是GpsLocationProvider类:

[java] view
plain copy

print?





frameworks\base\services\java\com\android\server\location\GpsLocationProvider.java

public class GpsLocationProvider implements LocationProviderInterface {

// 此处省略1000+N行

private ArrayList<Listener> mListeners = new ArrayList<Listener>();

private final class Listener implements IBinder.DeathRecipient {

final IGpsStatusListener mListener;

Listener(IGpsStatusListener listener) {

mListener = listener;

}

@Override

public void binderDied() {

if (DEBUG) Log.d(TAG, "GPS status listener died");

synchronized (mListeners) {

mListeners.remove(this);

}

if (mListener != null) {

mListener.asBinder().unlinkToDeath(this, 0);

}

}

}

/**

* called from native code to report NMEA data received

*/

private void reportNmea(long timestamp) {

synchronized (mListeners) {

int size = mListeners.size();

if (size > 0) {

// don't bother creating the String if we have no listeners

int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);

String nmea = new String(mNmeaBuffer, 0, length);

for (int i = 0; i < size; i++) {

Listener listener = mListeners.get(i);

try {

listener.mListener.onNmeaReceived(timestamp, nmea);

} catch (RemoteException e) {

Log.w(TAG, "RemoteException in reportNmea");

mListeners.remove(listener);

// adjust for size of list changing

size--;

}

}

}

}

}

}

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

print?





private native int native_read_nmea(byte[] buffer, int bufferSize);

native指明它是本地方法,和它对应的C/C++文件的实现是:

[cpp] view
plain copy

print?





static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jbyteArray nmeaArray, jint buffer_size);

How?Next...

[cpp] view
plain copy

print?





frameworks\base\services\jni\com_android_server_location_GpsLocationProvider.cpp

static JNINativeMethod sMethods[] = {

/* name, signature, funcPtr */

/* other members... */

{"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},

/* other members... */

};

JNINativeMethod是Android中采用的Java和C/C++函数的映射方式,并在其中描述了函数的参数和返回值:

[cpp] view
plain copy

print?





typedef struct {

const char* name; // Java文件中的本地方法

const char* signature; // 述了函数的参数和返回值

void* fnPtr; // 指针,指向具体的C/C++函数

} JNINativeMethod;

详细内容这里还是不展开了。

来看android_location_GpsLocationProvider_read_nmea()的实现:

[cpp] view
plain copy

print?





static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj,

jbyteArray nmeaArray, jint buffer_size)

{

// this should only be called from within a call to reportNmea

jbyte* nmea = (jbyte *)env->GetPrimitiveArrayCritical(nmeaArray, 0);

int length = sNmeaStringLength;

if (length > buffer_size)

length = buffer_size;

memcpy(nmea, sNmeaString, length);

env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT);

return length;

}

虽然不清楚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

print?





static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)

{

JNIEnv* env = AndroidRuntime::getJNIEnv();

// The Java code will call back to read these values

// We do this to avoid creating unnecessary String objects

sNmeaString = nmea;

sNmeaStringLength = length;

env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);

checkAndClearExceptionFromCallback(env, __FUNCTION__);

}

method_reportNmea、、、有没有熟悉的感觉?

对,在GpsLocationProvider类中见过reportNmea(long timestamp)函数。

下面的代码片段表明,method_reportNmea()和reportNmea()是绑定在一起的,调用C/C++函数method_reportNmea,也就间接调用Java的reportNmea()方法。这中间的机制,就是JNI!

[cpp] view
plain copy

print?





static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {

/* other definitions... */

method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");

/* other definitions... */

}

而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

print?





GpsCallbacks sGpsCallbacks = {

sizeof(GpsCallbacks),

location_callback,

status_callback,

sv_status_callback,

nmea_callback,

set_capabilities_callback,

acquire_wakelock_callback,

release_wakelock_callback,

create_thread_callback,

request_utc_time_callback,

};

GpsCallbacks结构体封装了所有需要回调的函数(确切的说是函数指针),sGpsCallbacks调用关系:

[cpp] view
plain copy

print?





static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)

{

// this must be set before calling into the HAL library

if (!mCallbacksObj)

mCallbacksObj = env->NewGlobalRef(obj);

// fail if the main interface fails to initialize

if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)

return false;

/* other codes */

return true;

}

而android_location_GpsLocationProvider_init()在GpsLocationProvider类中调用native_init()时被调用:

[cpp] view
plain copy

print?





static JNINativeMethod sMethods[] = {

/* name, signature, funcPtr */

{"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}

}

[cpp] view
plain copy

print?





这里,我们找到了和上层的关系,和下层如何打交道呢?

下面需要贴一大段代码:

[cpp] view
plain copy

print?





/** Represents the standard GPS interface. */

typedef struct {

/** set to sizeof(GpsInterface) */

size_t size;

/**

* Opens the interface and provides the callback routines

* to the implemenation of this interface.

*/

int (*init)( GpsCallbacks* callbacks );

/** Starts navigating. */

int (*start)( void );

/** Stops navigating. */

int (*stop)( void );

/** Closes the interface. */

void (*cleanup)( void );

/** Injects the current time. */

int (*inject_time)(GpsUtcTime time, int64_t timeReference,

int uncertainty);

/** Injects current location from another location provider

* (typically cell ID).

* latitude and longitude are measured in degrees

* expected accuracy is measured in meters

*/

int (*inject_location)(double latitude, double longitude, float accuracy);

/**

* Specifies that the next call to start will not use the

* information defined in the flags. GPS_DELETE_ALL is passed for

* a cold start.

*/

void (*delete_aiding_data)(GpsAidingData flags);

/**

* min_interval represents the time between fixes in milliseconds.

* preferred_accuracy represents the requested fix accuracy in meters.

* preferred_time represents the requested time to first fix in milliseconds.

*/

int (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,

uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);

/** Get a pointer to extension information. */

const void* (*get_extension)(const char* name);

} GpsInterface;

GpsInterface结构体封装了GPS实现的标准接口——接口,注意!接口不就时用来连接两端的吗?一端是com_android_server_location_GpsLocationProvider.cpp文件里的实现,那另一端就是。。。都探到这个地步了,另一端应该是串口方式直接和GPS芯片打交道的Linux驱动了吧?

确是,但是还需要一个媒介:

[cpp] view
plain copy

print?





struct gps_device_t {

struct hw_device_t common;

/**

* Set the provided lights to the provided values.

*

* Returns: 0 on succes, error code on failure.

*/

const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);

};

然后,

[cpp] view
plain copy

print?





static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {

int err;

hw_module_t* module;

/* other codes..*/

err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);

if (err == 0) {

hw_device_t* device;

err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);

if (err == 0) {

gps_device_t* gps_device = (gps_device_t *)device;

sGpsInterface = gps_device->get_gps_interface(gps_device);

}

}

/* other codes..*/

}

static JNINativeMethod sMethods[] = {

/* name, signature, funcPtr */

{"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},

}

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

print?





hardware\qcom\gps\loc_api\libloc_api\gps.c

struct hw_module_t HAL_MODULE_INFO_SYM = {

.tag = HARDWARE_MODULE_TAG,

.version_major = 1,

.version_minor = 0,

.id = GPS_HARDWARE_MODULE_ID,

.name = "loc_api GPS Module",

.author = "Qualcomm USA, Inc.",

.methods = &gps_module_methods,

};

有木有?GPS_HARDWARE_MODULE_ID!

[cpp] view
plain copy

print?





hardware\qcom\gps\loc_api\libloc_api\gps.c

extern const GpsInterface* gps_get_hardware_interface();

const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev)

{

return gps_get_hardware_interface();

}

static int open_gps(const struct hw_module_t* module, char const* name,

struct hw_device_t** device)

{

struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));

memset(dev, 0, sizeof(*dev));

dev->common.tag = HARDWARE_DEVICE_TAG;

dev->common.version = 0;

dev->common.module = (struct hw_module_t*)module;

dev->get_gps_interface = gps__get_gps_interface;

*device = (struct hw_device_t*)dev;

return 0;

}

static struct hw_module_methods_t gps_module_methods = {

.open = open_gps

};

流程很清楚了:

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

print?





static const GpsInterface mGpsInterface = {

.size =sizeof(GpsInterface),

.init = gps_init,

|--1、接收从上层传下来的GpsCallbacks变量,用它初始化GpsState->callbacks成员

|--2、GpsState结构体的其他成员初始化

|--3、GpsState->init状态设置为:STATE_INIT

|--4、最重要:启动GPS线程,进行数据的读取、处理:

state->thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);

--gps_create_thread create_thread_cb;

--typedef pthread_t (* gps_create_thread)(const char* name, void (*start)(void *), void* arg);

.start = gps_start,

--设置GPS的状态为开始:GPS_STATUS_SESSION_BEGIN

.stop = gps_stop,

--设置GPS的状态为结束:GPS_STATUS_SESSION_END

.cleanup = gps_cleanup,

--退出需要进行的一些清理工作,如GpsState->init = STATE_QUIT,GpsCallbacks指针归null,信号量回收

.inject_time = gps_inject_time,

--可为空函数

.inject_location = gps_inject_location,

--可为空函数

.delete_aiding_data = gps_delete_aiding_data,

--可为空函数

.set_position_mode = gps_set_position_mode,

--设置GPS工作模式:单GPS、单BD、GPS/BD双系统

.get_extension = gps_get_extension,

--定位之外的扩展功能实现

};

state->thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);

--static void gps_state_thread(void* arg):

1、state通过arg参数传入函数

2、创建了Time和Nmea数据处理两个线程

state->nmea_thread = state->callbacks.create_thread_cb("nmea_thread", gps_nmea_thread, state);

--static void gps_nmea_thread(void* arg)

--gps_opentty(state);

nmea_reader_init(reader);

--nmea_reader_parse(NmeaReader* r) {

if (gps_state->callbacks.nmea_cb) {

struct timeval tv;

unsigned long long mytimems;

gettimeofday(&tv,NULL);

mytimems = tv.tv_sec * 1000 + tv.tv_usec / 1000;

gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);

D("reader_parse. %.*s ", r->pos, r->in );

}

}

我们是从APP层NMEA信息输出自定向下分析的,APP层信息输出的最终起始是:gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);

到这里还有个问题:GPS芯片和CPU连接,使用的是哪个串口?这个串口号怎么确定的呢?
打算贴个完整HAL层的实例,考虑到代码很多,下篇在说吧。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: