【Android架构GPS篇】之定位数据怎样从GPS芯片到应用层
Android:V4.2.2 Source Insight
写在前面
在漫长的Android源代码编译等待过程中,想起之前写过一部分的Android定位实现的探究小品,于是继续探究。
注:代码都是片段化的代码,用来提纲挈领的说明问题。
定位的基础知识:
1、定位芯片和CPU之间通过串口进行通信
2、串口和CPU之间传输的是ASCII格式的NMEA(National Marine Electronics Association)信息,如:
$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:
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文件:
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):
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类的一个内部类。
仅仅看相关的部分:
// 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():
rameworks\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); }注:
onewaykeyword是用来修饰远程调用行为。使用该关键词时。远程调用不是堵塞的,它仅仅是发送事物数据并马上返回。
接口的终于实现是把普通的远程调用依照Binder线程池的调用规则来接收,假设oneway是使用在本地调用上,那么不会有不论什么影响,而且调用依旧是异步的。
以下。探究必须进入第三层。
三、GPS定位的Lib层实现
和IGpsStatusListener接头的是GpsLocationProvider类:
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类中定义:
private native int native_read_nmea(byte[] buffer, int bufferSize);native指明它是本地方法。和它相应的C/C++文件的实现是:
static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jbyteArray nmeaArray, jint buffer_size);How?Next...
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++函数的映射方式,并在当中描写叙述了函数的參数和返回值:
typedef struct { const char* name; // Java文件里的本地方法 const char* signature; // 述了函数的參数和返回值 void* fnPtr; // 指针,指向具体的C/C++函数 } JNINativeMethod;具体内容这里还是不展开了。
来看android_location_GpsLocationProvider_read_nmea()的实现:
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的设置过程:
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。
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的地方是:
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调用关系:
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()时被调用:
static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init} }
这里,我们找到了和上层的关系。和下层怎样打交道呢? 以下须要贴一大段代码:
/** 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驱动了吧?
确是。可是还须要一个媒介:
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); };然后,
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!
往下看:
ardware\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。
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应用层。
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层的实例,考虑到代码非常多,下篇在说吧。
。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架