• 【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 + "
    ");
    		}
    	};
    } 
    

    二、GPS定位的Framework层实现

    GpsStatus.NmeaListener是一个接口类。来自GpsStatus.java文件:

    frameworksaselocationjavaandroidlocationGpsStatus.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):

    frameworksaselocationjavaandroidlocationLocationManager.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():

    rameworksaselocationjavaandroidlocationIGpsStatusListener.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类:

    frameworksaseservicesjavacomandroidserverlocationGpsLocationProvider.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...

    frameworksaseservicesjnicom_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


    往下看:

    ardwareqcomgpsloc_apilibloc_apigps.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。
    hardwareqcomgpsloc_apilibloc_apigps.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层的实例,考虑到代码非常多,下篇在说吧。

    。。

  • 相关阅读:
    BurpSuite抓包问题
    如何共享磁盘文件呢?
    SQL Server:主键与外键设置与相关理解
    家丑不可外扬,这三种家丑,烂在肚子里也别说
    将Excel的数据导入SqlServer的表中
    我的人生感悟
    与人关系再好,也不要透露自己这3个秘密,对你没好处!
    count(1)、count(*)与count(列名)的执行区别
    远程桌面链接怎么共享本地磁盘
    IIs安装配置教程
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7144233.html
Copyright © 2020-2023  润新知